Servlet 是什么
Servlet 主要做的工作
- 允许程序猿注册一个类, 在 Tomcat 收到某个特定的 HTTP 请求的时候, 执行这个类中的一些代码.
- 帮助程序猿解析 HTTP 请求, 把 HTTP 请求从一个字符串解析成一个 HttpRequest 对象.
- 帮助程序猿构造 HTTP 响应. 程序猿只要给指定的 HttpResponse 对象填写一些属性字段, Servlet
- 就会自动的安装 HTTP 协议的方式构造出一个 HTTP 响应字符串, 并通过 Socket 写回给客户端.
第一个 Servlet 程序
1. 创建项目
创建一个maven项目, 名称目录自己设定
2. 引入依赖
Servlet 的版本要和 Tomcat 匹配.如果我们使用 Tomcat 8.5, 那么就需要使用 Servlet 3.1.0
把中央仓库中提供的 xml 复制到项目的 pom.xml 中
<dependencies>
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<dependencies>中央仓库提供的xml</dependencies>
3. 创建目录
1) 创建 webapp 目录
2) 创建WEB-INF 目录与web.xml文件
3) 编写 web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
</web-app>
4. 编写代码
在 java 目录中创建一个类 HelloServlet, 代码如下:
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//super.doGet(req, resp);
System.out.println("hello world");
resp.getWriter().println("hello world");
}
}
- 创建一个类 HelloServlet , 继承自 HttpServlet
- 在这个类上方加上 @WebServlet("/hello") 注解, 表示 Tomcat 收到的请求中, 路径为 /hello 的请求才会调用 HelloServlet 这个类的代码. (这个路径未包含 Context Path)
- 重写 doGet 方法. doGet 的参数有两个, 分别表示收到的 HTTP 请求 和要构造的 HTTP 响应. 这个方法会在 Tomcat 收到 GET 请求时触发
- HttpServletRequest 表示 HTTP 请求. Tomcat 按照 HTTP 请求的格式把 字符串 格式的请求转成了一个 HttpServletRequest 对象. 后续想获取请求中的信息(方法, url, header, body 等) 都是通过这个对象来获取.
- HttpServletResponse 表示 HTTP 响应. 代码中把响应对象构造好(构造响应的状态码, header, body 等)
- resp.getWriter() 会获取到一个流对象, 通过这个流对象就可以写入一些数据, 写入的数据会被 构造成一个 HTTP 响应的 body 部分, Tomcat 会把整个响应转成字符串, 通过 socket 写回给浏览器.
5. 打包程序
war 包和 jar 包的区别jar 包是普通的 java 程序打包的结果. 里面会包含一些 .class 文件.war 包是 java web 的程序, 里面除了会包含 .class 文件之外, 还会包含 HTML, CSS, JavaScript, 图片, 以及其他的 jar 包. 打成 war 包格式才能被 Tomcat 识别
我们需要在 pom.xml 中新增一个 packing 标签, 表示打包的方式是打一个 war 包
<packaging>war</packaging>
<build>
<finalName>java108_hello</finalName>
</build>
重新使用 maven 打包, 可以看到生成的新的 war 包的结果.
6. 部署程序
7. 验证程序
此时通过浏览器访问 http://127.0.0.1:8080/java108_hello/hello
注意: URL 中的 PATH 分成两个部分, 其中 java108_hello为 Context Path, hello 为 Servlet Path
更方便的部署方式
安装 Smart Tomcat 插件 
一个项目中, 第一次使用smart tomcat 需要简单配置一下
点击运行发现:
因为之前已经打开了tomcat ,占用了8080端口, 只要管关掉tomcat就可以了
实验:改动输出值和返回值
学习Servlet API
API就是一组类和方法 我们只需要学习3各类就可以
(1)HttpServlet
这是编写Servlet 代码用到的核心类, 通过继承这个类, 并重写其中的方法, 让tomcat去调用到这里的逻辑
核心方法
init 在 webapp 被加载的时候, 执行init (理论)
但是 tomcat 是可以配置webapp为 "懒加载" 的状态的
会使webapp在真正被访问到的时候才加载,(首次访问的时候, 才会触发 init)
destory webapp在被销毁的时候(tomcat结束)执行, 进行收尾工作
service 每次收到请求,都会执行Service 处理每个请求

- webapp 刚被加载的时候, 调用 servlet 的init 方法
- 每次收到请求的时候, 调用service方法
- webapp要结束的时候, 调用destroy方法
@WebServlet("/method")
public class MethodServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("doGet");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("doPost");
}
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("doPut");
}
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("doDelete");
}
}
针对上述方法, 浏览器只能比较方便的构造get请求, 不太方便构造其他的
针对其他的方法要想构造, 使用Ajax或者postman
GET
POST
(2)HttpServletRequest
核心方法

@WebServlet("/request")
public class RequestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 上面这个操作也是必要的, 显示告诉浏览器, 你拿到的数据是html
resp.setContentType("text/html");
// 调用req的各个方法, 把得到的结果汇总到一个字符串中, 统一返回到页面上
StringBuilder respBody = new StringBuilder();
// 下列内容实在浏览器上按照html的方式来展示, 此时 \n 在html中并不是换行
// 而是用<br> 标签表示换行
respBody.append(req.getProtocol());
respBody.append("<br>");
respBody.append(req.getMethod());
respBody.append("<br>");
respBody.append(req.getRequestURI());
respBody.append("<br>");
respBody.append(req.getContextPath());
respBody.append("<br>");
respBody.append(req.getQueryString());
respBody.append("<br>");
//拼接header
Enumeration<String> headers = req.getHeaderNames();
while(headers.hasMoreElements()) {
String header = headers.nextElement();
respBody.append(header + ":" + req.getHeader(header));
respBody.append("<br>");
}
//统一返回结果
resp.getWriter().write(respBody.toString());
}
}
当然更多时候, 是希望获取到query string 或者 body 中的内容(用户自定义)
1 下面代码就是直接获取query string
@WebServlet("/parameter")
public class ParameterServlet extends HttpServlet {
// 约定, 客户端使用 query string 传递数据
// query string 形如: username=zhangsan&password=123
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
System.out.println("username=" + username);
System.out.println("password=" + password);
resp.getWriter().write("ok");
}
}
2 下面代码就是直接获取body
@WebServlet("/parameter2")
public class Parameter2Servlet extends HttpServlet {
// 预期让客户端发送一个POST请求, 同时使用form 格式的数据, 在body中把数据传递过来
// body 形如:
// username=zhangsan&password=123
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
System.out.println("username" + username);
System.out.println("password" + password);
resp.getWriter().write("OK");
}
}
3 获取body (考虑body为json格式)
引入jackson库:
Jackson也是第三方库, 也需要通过maven从中央仓库把这个库下载来了导入到项目中
使用这个库的核心类就是ObjectMapper : 可以把一个对象映射到 JSON 字符串 也可以把 JSON 字符串映射到 对象
代码实现:
class User {
public String username;
public String password;
}
@WebServlet("/json")
public class JsonServlet extends HttpServlet {
// 此处约定客户端 body 按照 json 格式来进行传输
//其格式想入:
// {
// username : zhangsan
// password : 123
// }
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
// json => java对象
User user = objectMapper.readValue(req.getInputStream(), User.class);
System.out.println("username=" + user.username + ", password=" + user.password);
// Java对象 => json
String userString = objectMapper.writeValueAsString(user);
System.out.println("userJson=" + userString);
resp.getWriter().write("okk");
}
}
这个映射方法有许多版本, 作用就是把json字符串解析成Java对象, 其中这里的第一个参数, 是一个流对象, 也就是表示json从哪里来读.
第一个参数: http请求中的body(是通过 getInputStream() 方法得到流对象, 进一步读取出来的)
第二个参数, 则是指定的类型, 当我得到json字符串(第一个参数) 需要转成一个啥样的Java对象, 需要指定一下对象的类型
实验 :

能否使用 private 修饰变量
要想写作private 也可以, 但是必须要提供对应的getter和setter方法
class User {
private String username;
private String password;
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
}
@WebServlet("/json")
public class JsonServlet extends HttpServlet {
// 此处约定客户端 body 按照 json 格式来进行传输
//其格式想入:
// {
// username : zhangsan
// password : 123
// }
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
// json => java对象
User user = objectMapper.readValue(req.getInputStream(), User.class);
System.out.println("username=" + user.getUsername() + ", password=" + user.getPassword());
// Java对象 => json
String userString = objectMapper.writeValueAsString(user);
System.out.println("userJson=" + userString);
resp.getWriter().write("oKK");
}
}
如果不提供getter() 和 setter()方法, 就会出现状态500报错
获取和匹配的过程都发生在Jackson 的 readValue 工作过程中:
- 就是会先把json字符串(第一个参数)解析成键值对, 先放到Map中,
- 再根据参数填入的类对象(第二个参数) , 通过反射api就可以知道, 这个类里面有哪些public 修饰的属性, 每个属性的名字和类型
- 一次把这里的每个属性都取出来, 通过属性名字查询上述的Map, 把得到的值, 赋值给这个类的属性
由于把username 改成 private了, 而Jackson 并不会直接针对private 属性进行扫描, username就不认识了
(3)HttpServletResponse
同样也是和HTTP响应数据, 是匹配的
响应数据中的状态码, 各种header, body 针对这些属性, 就可以进行设置
请求对象, 我们拿到之后的母的, 是为了获取里面的属性(读)
响应对象, 我们拿到之后的目的, 是为了设置里 面的属性(写)
对于 doXXX这样的方法来说, 本身要做的事情就是"根据请求 计算响应"
请求对象, 是Tomcat 收到请求之后, 对http 协议解析得到的对象
响应对象, 是Tomcat 创建的空的对象, 我们在代码中把 响应对象的属性设置好
(1) 状态码
@WebServlet("/status")
public class StatusServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setStatus(404);
}
}
其是是可以在返回 状态码的同时, 给body也写入数据的, 就可以得到一些"个性化的错误页面"
(2) 刷新页面
@WebServlet("/refresh")
public class RefreshServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setHeader("refresh", "2");
resp.getWriter().write("time" + System.currentTimeMillis());
}
}
refresh : 2 ==> 浏览器就会每隔两秒自动刷新一次
设置这个属性之后, 此时, 每隔2s就会自动刷新一次, 但是这里的刷新间隔也不是精确的2000ms, 会比2000稍微长点, 毕竟, 浏览器发起请求, 服务器响应, 再到页面被解析出来, 都是需要消耗一定的时间
(3) 重定向 : 请求会返回重定向位置的页面
@WebServlet("/redirect")
public class RedirectServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 让页面被重定向 搜狗主页
resp.setStatus(302);
// 重定向响应,一定要带有 Location , 表示要重定向到哪里
resp.setHeader("Location", "https://www.bilibili.com");
}
}
(4) 设置resp body
@WebServlet("/body")
public class BodyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 让服务器返回一个 html 数据
resp.getWriter().write("<div>你好</div>");
}
}
l浏览器吗默认会跟随系统的编码
windows简体中文版, 默认的编码是gbk
拿着utf8的数据, 浏览器按照gbk的方式来解析, 势必就会出现乱码
解决乱码的原则, 就是编码方式匹配~~
utf8是更主流的编码方式, 而gbk这种编码只能表示简体中文
@WebServlet("/body")
public class BodyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html; charset=utf8");
// 让服务器返回一个 html 数据
resp.getWriter().write("<div>你好</div>");
}
}