Servlet API 详解
- 一. HttpServlet
- 1. 核心方法
- 2. 代码示例: 处理 GET 请求
- 3. 关于乱码问题
- 4. 代码示例: 处理 POST 请求
- 二. HttpServletRequest
- 1. 核心方法
- 2. 代码示例: 打印请求信息
- 3. 代码示例: 获取 GET 请求中的参数
- 4. 代码示例: 获取 POST 请求中的参数(1)
- 5. 代码示例: 获取 POST 请求中的 JSON (2)
- 6. 代码示例: 获取 POST 请求中的 JSON(3)
- 三. HttpServletResponse
- 1. 核心方法
- 2. 代码示例: 设置状态码
- 3. 代码示例: 自动刷新
- 4. 代码示例: 重定向
一. HttpServlet
1. 核心方法
方法名称 | 调用时机 |
---|---|
init | 在 HttpServlet 实例化之后被调用一次 |
destory | 在 HttpServlet 实例不再使用的时候调用一次 |
service | 收到 HTTP 请求的时候调用 |
doGet | 收到 GET 请求的时候调用(由 service 方法调用) |
doPost | 收到 POST 请求的时候调用(由 service 方法调用) |
doPut/doDelete/doOptions/… | 收到其他请求的时候调用(由 service 方法调用) |
我们实际开发的时候主要重写 doXXX 方法, 很少会重写 init / destory / service .
这些方法的调用时机, 就称为 “Servlet 生命周期”. (也就是描述了一个 Servlet 实例从生到死的过程).
注意: HttpServlet 的实例只是在程序启动时创建一次. 而不是每次收到 HTTP 请求都重新创建实例.
2. 代码示例: 处理 GET 请求
创建 MethodServlet.java, 创建 doGet 方法
@WebServlet("/method")
public class MethodServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.getWriter().write("GET response");
}
}
创建 testMethod.html, 放到 webapp 目录中, 形如
testMethod.html 内容:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<button onclick="sendGet()">发送 GET 请求</button>
<script>
function sendGet() {
ajax({
method: 'GET',
url: 'method',
callback: function (body, status) {
console.log(body);
}
});
}
// 把之前封装的 ajax 函数拷贝过来
function ajax(args) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
// 0: 请求未初始化
// 1: 服务器连接已建立
// 2: 请求已接收
// 3: 请求处理中
// 4: 请求已完成,且响应已就绪
if (xhr.readyState == 4) {
args.callback(xhr.responseText, xhr.status)
}
}
xhr.open(args.method, args.url);
if (args.contentType) {
xhr.setRequestHeader('Content-type', args.contentType);
}
if (args.body) {
xhr.send(args.body);
} else {
xhr.send();
}
}
</script>
</body>
</html>
重新部署程序, 使用 URL http://localhost:8080/test/testMethod.html 访问页面
注意:
- http://localhost:8080/test/testMethod.html 这个路径里面的 /test 是自己定义的 Context Path
- 这个 ajax 请求的 URL 路径. 代码中写的 URL url: ‘method’, 为一个相对路径, 最终真实发
送的请求的 URL 路径为 /test/method
点击 “发送 GET 请求” 按钮, 即可在控制台看到响应内容.
通过 Fiddler 抓包, 可以看到,
-
当浏览器中输入 URL 之后, 浏览器先给服务器发送了一个 HTTP GET 请求
-
当点击 “发送 GET 请求” 按钮, 浏览器又通过 ajax 给服务器发送了一个 HTTP GET 请求
3. 关于乱码问题
如果我们在响应代码中写入中文, 例如
resp.getWriter().write("GET 响应");
得到的结果:
关于 “乱码”:
- 中文的编码方式有很多种. 其中最常见的就是 utf-8 .
- 如果没有显式的指定编码方式, 则浏览器不能正确识别编码, 就会出现乱码的情况.
- 可在代码中, 通过 resp.setContentType(“text/html; charset=utf-8”); 显式指定编码方式.
@WebServlet("/method")
public class MethodServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html; charset=utf-8");
resp.getWriter().write("GET 响应");
}
}
更改后的响应
通过抓包可以看到, 当加上了 resp.setContentType(“text/html; charset=utf-8”); 代码之后, 响应中多了 Content-Type 字段, 内部指定了编码方式. 浏览器看到这个字段就能够正确解析中文了.
4. 代码示例: 处理 POST 请求
在 MethodServlet.java 中, 新增 doPost 方法.
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
resp.setContentType("text/html; charset=utf-8");
resp.getWriter().write("POST 响应");
}
在 testMethod.html 中, 新增一个按钮, 和对应的点击事件处理函数
<button onclick="sendPost()">发送 POST 请求</button>
<script>
function sendPost() {
ajax({
method: 'POST',
url: 'method',
callback: function (body, status) {
console.log(body);
}
})
}
</script>
重新部署程序, 使用 URL http://127.0.0.1:8080/test/testMethod.html 访问页面.
点击 “发送 POST 请求” 按钮, 可以在控制台中看到结果:
通过类似的方式还可以验证 doPut, doDelete 等方法.
二. HttpServletRequest
当 Tomcat 通过 Socket API 读取 HTTP 请求(字符串), 并且按照 HTTP 协议的格式把字符串解析成 HttpServletRequest 对象.
1. 核心方法
方法 | 描述 |
---|---|
String getProtocol() | 返回请求协议的名称和版本。 |
String getMethod() | 返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT。 |
String getRequestURI() | 从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请求的 URL 的一部分。 |
String getContextPath() | 返回指示请求上下文的请求 URI 部分。(一级路径) |
String getQueryString() | 返回包含在路径后的请求 URL 中的查询字符串。 |
EnumerationgetParameterNames() | 返回一个 String 对象的枚举,包含在该请求中包含的参数的名称。 |
String getParameter(String name) | 以字符串形式返回请求参数的值,或者如果参数不存在则返回 null。 |
String[] getParameterValues(String name) | 返回一个字符串对象的数组,包含所有给定的请求参数的值,如果参数不存在则返回 null。 |
Enumeration getHeaderNames() | 返回一个枚举,包含在该请求中包含的所有的头名。 |
String getHeader(String name) | 以字符串形式返回指定的请求头的值。 |
String getCharacterEncoding() | 返回请求主体中使用的字符编码的名称。 |
String getContentType() | 返回请求主体的 MIME 类型,如果不知道类型则返回 null。 |
int getContentLength() | 以字节为单位返回请求主体的长度,并提供输入流,或者如果长度未知则返回 -1。 |
InputStream getInputStream() | 用于读取请求的 body 内容. 返回一个 InputStream 对象. |
通过这些方法可以获取到一个请求中的各个方面的信息.
注意: 请求对象是服务器收到的内容, 不应该修改. 因此上面的方法也都只是 “读” 方法, 而不是 “写” 方法.
2. 代码示例: 打印请求信息
创建 ShowRequest 类
@WebServlet("/showRequest")
public class ShowRequest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html; charset=utf-8");
StringBuilder respBody = new StringBuilder();
respBody.append(req.getProtocol()); // 请求协议的名称和版本
respBody.append("<br>");
respBody.append(req.getMethod()); // 请求的 HTTP 方法
respBody.append("<br>");
respBody.append(req.getRequestURI()); // 从协议名称直到 HTTP 请求的第一行的查询字符串
respBody.append("<br>");
respBody.append(req.getContextPath()); // 上下文 (一级路径)
respBody.append("<br>");
respBody.append(req.getQueryString()); // URL 中的查询字符串
respBody.append("<br>");
respBody.append("<h3>headers:</h3>");
Enumeration<String> headerNames = req.getHeaderNames(); // 请求中包含的所有的头名
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
respBody.append(headerName + " ");
respBody.append(req.getHeader(headerName));
respBody.append("<br>");
}
resp.getWriter().write(respBody.toString());
}
}
部署程序. 在浏览器通过 URL http://127.0.0.1:8080/test/showRequest 访问, 可以看到
3. 代码示例: 获取 GET 请求中的参数
GET 请求中的参数一般都是通过 query string 传递给服务器的. 形如 https://www.baidu.com/personInf/student?userId=1111&classId=100
此时浏览器通过 query string 给服务器传递了两个参数, userId 和 classId, 值分别是 1111 和 100
在服务器端就可以通过 getParameter 来获取到参数的值.
创建 GetParameter 类
@WebServlet("/getParameter")
public class GetParameter extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html; charset=utf-8");
String userId = req.getParameter("userId");
String classId = req.getParameter("classId");
resp.getWriter().write("userId: " + userId + ", " + "classId: " +
classId);
}
}
重新部署程序, 在浏览器中通过 http://127.0.0.1:8080/test/getParameter 访问, 可以看到:
当没有 query string的时候, getParameter 获取的值为 null.
如果通过 http://127.0.0.1:8080/test/getParameter?userId=123&classId=456 访问, 可以看到
此时说明服务器已经获取到客户端传递过来的参数.
getParameter 的返回值类型为 String. 必要的时候需要手动把 String 转成其他类型
4. 代码示例: 获取 POST 请求中的参数(1)
POST 请求的参数一般通过 body 传递给服务器. body 中的数据格式有很多种. 如果是采用 form 表单的形式, 仍然可以通过 getParameter 获取参数的值.
创建类 PostParameter
@WebServlet("/postParameter")
public class PostParameter extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html; charset=utf-8");
String userId = req.getParameter("userId");
String classId = req.getParameter("classId");
resp.getWriter().write("userId: " + userId + ", " + "classId: " +
classId);
}
}
创建 testPost.html, 放到 webapp 目录中
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<form action="postParameter" method="POST">
<input type="text" name="userId">
<input type="text" name="classId">
<input type="submit" value="提交">
</form>
</body>
</html>
重新部署程序, 通过 URL http://127.0.0.1:8080/test/testPost.html 访问, 可以看到 HTML
在输入框中输入内容, 点击提交
可以看到跳转到了新的页面, 并显示出了刚刚传入的数据.
此时通过抓包可以看到, form 表单构造的 body 数据的格式为:
5. 代码示例: 获取 POST 请求中的 JSON (2)
如果 POST 请求中的 body 是按照 JSON 的格式来传递, 那么获取参数的代码就要发生调整.
创建 PostParameterJson 类
@WebServlet("/postParameterJson")
public class PostParameterJson extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("application/json;charset=utf-8");
String body = readBody(req);
resp.getWriter().write(body);
}
private String readBody(HttpServletRequest req) throws IOException {
int contentLength = req.getContentLength();
byte[] buffer = new byte[contentLength];
InputStream inputStream = req.getInputStream();
inputStream.read(buffer);
return new String(buffer, "utf-8");
}
}
创建 testPostJson.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<button onclick="sendJson()">发送 JSON 格式 POST 请求</button>
<script>
function sendJson() {
ajax({
url: 'postParameterJson',
method: 'POST',
contentType: 'application/json; charset=utf-8',
body: JSON.stringify({ userId: 123, classId: 456 }),
callback: function (body, status) {
console.log(body);
}
});
}
function ajax(args) {
// 函数体略.... 参考之前封装的版本.
}
</script>
</body>
</html>
在浏览器中通过 http://127.0.0.1:8080/test/testPostJson.html 访问, 可以看到
点击按钮, 则浏览器就会给服务器发送一个 POST 请求, body 中带有 JSON 格式.
响应:
注意: 到目前为止, 服务器拿到的 JSON 数据仍然是一个整体的 String 类型, 如果要想获取到 userId 和 classId 的具体值, 还需要搭配 JSON 库进一步解析, 还不能根据 key 获取 value
6. 代码示例: 获取 POST 请求中的 JSON(3)
引入 Jackson 这个库, 进行 JSON 解析.
-
在中央仓库中搜索 Jackson, 选择 JackSon Databind
-
把中央仓库中的依赖配置添加到 pom.xml 中, 形如:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.3</version>
</dependency>
- 在 PostParameterJson 类中修改代码
// 创建一个新的类表示 JSON 数据, 属性的名字需要和 JSON 字符串中的 key 一致.
class JsonData {
public String userId;
public String classId;
}
@WebServlet("/postParameterJson")
public class PostParameterJson extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html;charset=utf-8");
String body = readBody(req);
// 创建 ObjectMapper 对象. 这个是 Jackson 中的核心类.
ObjectMapper objectMapper = new ObjectMapper();
// 通过 readValue 方法把 body 这个字符串转成 JsonData 对象
JsonData jsonData = objectMapper.readValue(body, JsonData.class);
resp.getWriter().write("userId: " + jsonData.userId + ", " + "classId: "
+ jsonData.classId);
}
private String readBody(HttpServletRequest req) throws IOException {
int contentLength = req.getContentLength();
byte[] buffer = new byte[contentLength];
InputStream inputStream = req.getInputStream();
inputStream.read(buffer);
return new String(buffer, "utf-8");
}
}
结果:
注意:
- JsonData 这个类用来表示解析之后生成的 JSON 对象. 这个类的属性的名字和类型要和 JSON 字符串的 key 相对应.
- Jackson 库的核心类为 ObjectMapper. 其中的 readValue 方法把一个 JSON 字符串转成 Java 对象. 其中的 writeValueAsString 方法把一个 Java 对象转成 JSON 格式字符串.
- readValue 的第二个参数为 JsonData 的 类对象. 通过这个类对象, 在 readValue 的内部就可以借助反射机制来构造出 JsonData 对象, 并且根据 JSON 中 key 的名字, 把对应的 value 赋值给 JsonData 的对应相同的字段.
三. HttpServletResponse
Servlet 中的 doXXX 方法的目的就是根据请求计算得到相应, 然后把响应的数据设置到 HttpServletResponse 对象中.
然后 Tomcat 就会把这个 HttpServletResponse 对象按照 HTTP 协议的格式, 转成一个字符串, 并通过 Socket 写回给浏览器.
1. 核心方法
方法 | 描述 |
---|---|
void setStatus(int sc) | 为该响应设置状态码。 |
void setHeader(String name, String value) | 设置一个带有给定的名称和值的 header. 如果 name 已经存在,则覆盖旧的值. |
void addHeader(String name, String value) | 添加一个带有给定的名称和值的 header. 如果 name 已经存在,不覆盖旧的值, 并列添加新的键值对 |
void setContentType(String type) | 设置被发送到客户端的响应的内容类型。 |
void setCharacterEncoding(String charset) | 设置被发送到客户端的响应的字符编码(MIME 字符集)例如,UTF-8。 |
void sendRedirect(String location) | 使用指定的重定向位置 URL 发送临时重定向响应到客户端。 |
PrintWriter getWriter() | 用于往 body 中写入文本格式数据 |
OutputStream getOutputStream() | 用于往 body 中写入二进制格式数据. |
注意: 响应对象是服务器要返回给浏览器的内容, 这里的重要信息都是程序员设置的. 因此上面的方法都是 “写” 方法.
注意: 对于状态码/响应头的设置要放到 getWriter / getOutputStream 之前. 否则可能设置失效.
2. 代码示例: 设置状态码
创建 StatusServlet 类
@WebServlet("/statusServlet")
public class StatusServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String statusString = req.getParameter("status");
if (statusString != null) {
resp.setStatus(Integer.parseInt(statusString));
}
resp.getWriter().write("status: " + statusString);
}
}
部署程序, 在浏览器中通过 URL http://127.0.0.1:8080/test/statusServlet?status=200 访问, 可以看到
抓包结果:
变换不同的 status 的值, 就可以看到不同的响应结果.
3. 代码示例: 自动刷新
实现一个程序, 让浏览器每秒钟自动刷新一次. 并显示当前的时间戳.
创建 AutoRefreshServlet 类
@WebServlet("/autoRefreshServlet")
public class AutoRefreshServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setHeader("Refresh", "1");
long timeStamp = new Date().getTime();
resp.getWriter().write("timeStamp: " + timeStamp);
}
}
- 通过 HTTP 响应报头中的 Refresh 字段, 可以控制浏览器自动刷新的时机.
- 通过 Date 类的 getTime 方法可以获取到当前时刻的毫秒级时间戳.
部署程序, 通过 URL http://127.0.0.1:8080/test/autoRefreshServlet 访问, 可以看到浏览器每秒钟自动刷新一次.
抓包结果
4. 代码示例: 重定向
返回一个重定向 HTTP 响应, 自动跳转到另外一个页面.
创建 RedirectServlet 类
@WebServlet("/redirectServlet")
public class RedirectServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.sendRedirect("http://www.sogou.com");
}
}
部署程序, 通过 URL http://127.0.0.1:8080/test/redirectServlet 访问, 可以看到, 页面自动跳转到 搜狗主页 了.
抓包结果
好啦! 以上就是对 Servlet API 的讲解,希望能帮到你 !
评论区欢迎指正 !