在 HttpServlet 中 有三个核心的方法,是 init、destory、service。
init 方法
@WebServlet("/method")
public class servletMethond extends HttpServlet {
@Override
public void init() throws ServletException {
// 重写 inti 方法
System.out.println("init");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doGet");
resp.getWriter().write("doGet");
}
}
init 方法与 doGet 都是 HttpServlet 类里的方法,也是 tomcat 调用的, 同样都是可以重写的。
tomcat 收到了 /method 这样的路径的请求,就会调用到 servletMethond。
于是就需要先对 servletMethond 进行实例化,这个实例化只进行一次。
后续再收到 /method 此时就不必在重复实例化了,直接复用之前的 servletMethond 实例即可。
这个方法在 HttpServlet 实例化之后只会被调用一次,
也就是说,即使是多次请求也只会出现一个init。
\
开启服务器,可以看到此时只有一个 init,下面来多次刷新页面观察效果。
可以看到 doGet 会随着刷新次数增加,但是 init 只会在最开始的时候出现一次。
destroy 方法
在服务器终止的时候就会调用这个方法。
下面重写这个来演示一下。
@Override
public void destroy() {
System.out.println("destroy");
}
可以看到启动服务器的时候,并没有调用 destroy 这个方法,所以就没有显示出这个方法里的内容。
点击红色的矩形终止服务器。
可以看到终止服务器之后,就成功的调用了这个方法。
会不会出现 destroy 是不确定的。
如果是通过 smart tomcat 停止按钮,这个操作本质上是通过 tomcat 的 8005 端口主动停止,
是能够调用 destroy 这个方法,触发 destroy 的。
如果是直接杀进程,此时就可能来不及执行,此时 destroy 就没了。
service 方法
在收到路径匹配的 http 请求就会调用这个方法。
service 这个方法里包含了一个 doGet 方法,也就是说 doGet 方法就是在 service 方法中调用的。
接下来重写 service 方法观看底层代码。
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.service(req, resp);
}
这是一个比较频繁的面试题,生命周期就是在什么阶段,做什么事了。
比如一个人的生命周期:
1.小时候,要做的就是上学。
2.长大了之后,要参加工作。
3.再大一点,要结婚生娃。
4.年纪再大,娃要上学了。
5.年纪再大,娃娃要结婚了。
6.年纪再大一点,帮娃带娃。
7.ji了。
Servlet 的生命周期就是:
1.开始的时候,执行 init。
2.每次收到请求,执行 service。
3.销毁之前,执行 destroy。
先来重写 doGet、doPost、doPut、doDelete 方法。
@WebServlet("/methods")
public class MyMethod extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doGet");
resp.getWriter().write("doGet");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doPost");
resp.getWriter().write("doPost");
}
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doPut");
resp.getWriter().write("doPut");
}
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("doGet");
resp.getWriter().write("doDelete");
}
}
可以看到此时只显示出了一个 doGet,那其他的请求怎么办呢?
这个时候就可以使用 postman 与 ajax 来构造请求。
输入路径,选择 GET 请求,点击 send 就构造好了一个 GET 请求,就出现了一个 doGet。
如果要构造其他的请求,可以更改即可。
在 webapp 目录下创建一个 .html 的文件。
绝对路径的写法:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ajaxtitle>
head>
<body>
<script src="https://code.jquery.com/jquery-3.6.4.min.js">script>
<script>
$.ajax({
type: 'get',
url: 'methods',
success: function(body, status) {
console.log(body);
}
});
script>
body>
html>
此处所写的 methods 就相当于是在 http://127.0.0.1:8080/Servlet 基础上再拼上一个 methods。
也就是 http://127.0.0.1:8080/Servlet/methods
在地址栏输入路径,打开控制台就可以看到构造好的 GET 请求了。
相对路径的写法:
url: '/Servlet/methods',
接下来使用 ajax 构造 POST 请求。
<body>
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<script>
$.ajax({
type: 'post',
url: 'methods',
success: function(body, status) {
console.log(body);
}
});
</script>
</body>
</html>
Request 表示的是 HTTP 请求,HttpServlet 这个对象是 tomcat 自动创造的。
tomcat 其实会实现监听端口,接受连接,读取请求,解析请求,构造请求对象等一系列的工作。
核心方法
@WebServlet("/showRequest")
public class ShowRequestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 使用 StringBuilder 把这些 api 结果拼接起来,统一写响应中
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(req.getProtocol());
stringBuilder.append(req.getMethod());
stringBuilder.append(req.getRequestURI());
stringBuilder.append(req.getContextPath());
stringBuilder.append(req.getQueryString());
// 写回到响应中
resp.getWriter().write(stringBuilder.toString());
}
}
getProtocol 得到的是 HTTP/1.1、getMethod 得到的是 GET
getRequestURI 得到的是 /Servlet/showRequest、getContextPath 得到的是 /Servlet
getQueryString 得到的是 null。
在前端给后端传两个数字,一个是同学的 studentId,一个是 classId。
发送一个 ?studentId=10&classId=20 这样的请求。
@WebServlet("/getParameter")
public class GetParameterServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 预期浏览器会发一个形如 /getParameter?studentId=10&classId=20 请求
// 借出 req 里的 getParameter 方法就能拿到 query string 中的键值对内容了
// getParameter 得到的是 string 类型的结果
String studentId = req.getParameter("studentId");
String classId = req.getParameter("classId");
resp.setContentType("text/html");
resp.getWriter().write("studentId = " + studentId + "classId = " + classId);
}
}
在地址栏中输入路径访问。
我这里的路径是 127.0.0.1:8080/Servlet/getParameter?studentId=10&classId=20
通过 getParameter 方法,?studentId=10&classId=20 这个键值对会自动被 tomcat 处理成
形如 Map 这样的结构,后续就可以直接通过 key 获取 value 了。
如果 key 在 query string 中不存在,此时返回的就是 null。
如果前端是 form 表单格式的数据,后端还是使用 getParameter 来获取。
这里的 form 表单格式的数据也是键值对,和 query string 的格式是一样的,只是这部分内容在 body 中。
<form action="postParameter" method="post">
<input type="text" name="studentId">
<input type="text" name="classId">
<input type="submit" value="提交">
form>
在输入框中输入以下的数据。
当前显示 404 是因为 postParameter 还没有实现,但是不影响,打开 fiddler 抓包观察即可。
当前代码中的 action 属性里的 postParameter 得到的就是抓包结果中的 postParameter 这个路径。
method 属性 得到的就是抓包结果中的 POST 。
input 标签里的 name 属性里的 studentId 和 classId 实现的就是最下方的 studentId=10&classId=20。
而在输入框中输入的内容及决定了它们两个的值。
使用 getParameter 既可以获取到 query string 中的键值对,也可以获取到 form 表单构造的 body 中的键值对。
@WebServlet("/getparameter")
public class PostGetParameterServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String studentId = req.getParameter("studentId");
String classId = req.getParameter("classId");
resp.setContentType("text/html");
resp.getWriter().write("studentId = " + studentId + "classId = " + classId);
}
}
json 是一种非常主流的数据格式,也是键值对结构。
{
classId: 20,
studentId: 10
}
可以把 body 按照上面的格式来组织。
前段可以通过 ajax 的方式来构造出这个内容,也可以使用更简单的 postman 的方式。
打开 postman,勾选上述的选项,之后点击发送即可,由于还没有实现 getparameter2 的代码,
所以此时发送后,会报一个 404 错误。
接下来实现 getparameter2 的代码。
@WebServlet("/getparameter2")
public class PostParameter2 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 通过 getInputStream 把 req 对象里的 body 完整读取出来
// 在流对象中读取多少个字节,取决于 Content-Length
int length = req.getContentLength(); // 这是 body 实际的长度
// 创建一个相同长度的字节数组
byte[] buffer = new byte[length];
// 借出 inputStream 来读取 body 的内容
InputStream inputStream = req.getInputStream();
inputStream.read(buffer);
// 把这个字节数组转成 String,然后打印出来
String body = new String(buffer, 0, length, "utf8");
System.out.println("body =" + body);
resp.getWriter().write(body);
}
}
通过 Content-Length 得到 body 的长度,然后再按照这个长度从请求对象中读取数据,
于是就把 body 给读取出来了。
可以看到服务器与客户端都有了结果。
服务器这里打印的结果就是,从 请求 body 里读取的内容。
接下来使用 fiddler 抓包观察结果
理解它的流程
json 与 form 的区别:
json 格式代码的执行流程与上面通过 form表单传参的流程是类似的,只不过是传参的数据格式不同。
form 表单是形如 classId=20&studentId=10。
json 格式则是形如:
{
classId: 20,
studentId: 10
}
当前通过 json 传递数据,服务器只是把整个 body 读出来了,但是没有按照键值对的的方式来处理,
也就是说,目前还不能根据 key 获取 value。
此时建议使用 第三方库 —— jackson。
1、通过 https://releases.jquery.com/ 打开 maven 仓库
4、将 Maven 里的代码复制到 pom.xml 文件里的 dependency 标签里
接下来尝试更改代码的写法。
class Student {
public int studentId;
public int classId;
}
@WebServlet("/getparameter2")
public class PostParameter2 extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 使用 jackson 涉及到的核心对象
ObjectMapper objectMapper = new ObjectMapper();
// readValue 可以把一个 jackson 格式的字符串转成 java 对象
Student student = objectMapper.readValue(req.getInputStream(), Student.class);
System.out.println(student.studentId + "," + student.classId);
}
}
Student student = objectMapper.readValue(req.getInputStream(), Student.class);
这条语句中的 readValue 方法,会执行下面的流程。
1、从 body 中读取 json 格式的字符串
{
classId: 20,
studentId: 10
}
2、根据第二个参数类对象,创建 Student 实例
3、解析上述的 json 格式的字符串,处理成 map 键值对结构
4、遍历所有的键值对,看键的名字和 Student 实例的哪个属性名字匹配,
就把对应的 value 设置到该属性中
5、返回该 Student 实例
如果想让下面的代码显示出中文,此时就需要设置字符集 为 utf-8
@WebServlet("/getParameter")
public class GetParameterServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 预期浏览器会发一个形如 /getParameter?studentId=10&classId=20 请求
// 借出 req 里的 getParameter 方法就能拿到 query string 中的键值对内容了
// getParameter 得到的是 string 类型的结果
String studentId = req.getParameter("studentId");
String classId = req.getParameter("classId");
resp.setContentType("text/html");
resp.getWriter().write("学生Id = " + studentId + "班级Id = " + classId);
}
}
接下来开始调用 setCharacterEncoding 设置字符集。
resp.setCharacterEncoding("utf-8");
将上述的代码中添加上这条语句即可。
刷新页面可以看到正确的显示出来了。
需要注意的是 设置字符集那条语句必须是在 getWriter().write() 的上面。
如果写在了下面是不会生效的。
也可以把字符集和 ContentType 一起设置
resp.setContentType("text/html; charset = utf-8");
以 3 开头的状态码,浏览器会自动跳转到指定的新地址。
@WebServlet("/redirectServlet")
public class RedirectServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.sendRedirect("https://www.sogou.com");
}
}
在地址栏中输入地址后,会跳转到指定的新地址。以上就是会跳转到搜狗的页面。
打开 fiddler 抓包并观察结果。
代码实现的第二种方式
resp.setStatus(302);
resp.setHeader("Location", "https://www.sogou.com/");
将代码修改为以上两句即可,这种写法是将上面的步骤给拆分成两步了。