javaEE初阶 — Servlet API 详解

た 入场券 2023-10-09 17:22 130阅读 0赞

文章目录

  • HttpServlet
    • 1 Servlet 的生命周期
    • 2 代码示例
    • 3 使用 postman 构造请求
    • 4 使用 ajax 构造请求
  • HttpServletRequest
    • 1 代码示例
  • 前端如何给后端传参
    • 1 通过 GET 里的 query string 传参
    • 2 通过 POST 借助 form 表单传参
    • 3 通过 json 格式传参
  • HttpServletResponse
    • 1 代码示例
      • 1.1 设置字符集
      • 1.2 重定向

HttpServlet

在 HttpServlet 中 有三个核心的方法,是 init、destory、service

init 方法

  1. @WebServlet("/method")
  2. public class servletMethond extends HttpServlet {
  3. @Override
  4. public void init() throws ServletException {
  5. // 重写 inti 方法
  6. System.out.println("init");
  7. }
  8. @Override
  9. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  10. System.out.println("doGet");
  11. resp.getWriter().write("doGet");
  12. }
  13. }

init 方法与 doGet 都是 HttpServlet 类里的方法,也是 tomcat 调用的, 同样都是可以重写的。

tomcat 收到了 /method 这样的路径的请求,就会调用到 servletMethond
于是就需要先对 servletMethond 进行实例化,这个实例化只进行一次。
后续再收到 /method 此时就不必在重复实例化了,直接复用之前的 servletMethond 实例即可。

这个方法在 HttpServlet 实例化之后只会被调用一次
也就是说,即使是多次请求也只会出现一个init。
\

2f18d6bfdce54cd0b2227e5bcc08cfce.png_pic_center

开启服务器,可以看到此时只有一个 init,下面来多次刷新页面观察效果。

aee5184665ee4a8ca87f4a3fd5d6a169.png_pic_center

可以看到 doGet 会随着刷新次数增加,但是 init 只会在最开始的时候出现一次。

destroy 方法

在服务器终止的时候就会调用这个方法。

下面重写这个来演示一下。

  1. @Override
  2. public void destroy() {
  3. System.out.println("destroy");
  4. }

317db5ea50cd4beb9eb47ebc27324630.png_pic_center
可以看到启动服务器的时候,并没有调用 destroy 这个方法,所以就没有显示出这个方法里的内容。

点击红色的矩形终止服务器。

056260106b5f44da987b37f830c8beb4.png_pic_center

可以看到终止服务器之后,就成功的调用了这个方法。

会不会出现 destroy 是不确定的。

如果是通过 smart tomcat 停止按钮,这个操作本质上是通过 tomcat 的 8005 端口主动停止,
是能够调用 destroy 这个方法,触发 destroy 的。
如果是直接杀进程,此时就可能来不及执行,此时 destroy 就没了。

service 方法

在收到路径匹配的 http 请求就会调用这个方法。

service 这个方法里包含了一个 doGet 方法,也就是说 doGet 方法就是在 service 方法中调用的。

接下来重写 service 方法观看底层代码。

  1. @Override
  2. protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  3. super.service(req, resp);
  4. }

1ba222fae3bc4a8ca80501f2c24e6eae.png_pic_center

可以看到有一个 diGet 方法。

1 Servlet 的生命周期

这是一个比较频繁的面试题,生命周期就是在什么阶段,做什么事了。

比如一个人的生命周期:

1.小时候,要做的就是上学。
2.长大了之后,要参加工作。
3.再大一点,要结婚生娃。
4.年纪再大,娃要上学了。
5.年纪再大,娃娃要结婚了。
6.年纪再大一点,帮娃带娃。
7.ji了。

Servlet 的生命周期就是:

1.开始的时候,执行 init。
2.每次收到请求,执行 service。
3.销毁之前,执行 destroy。

2 代码示例

先来重写 doGet、doPost、doPut、doDelete 方法。

  1. @WebServlet("/methods")
  2. public class MyMethod extends HttpServlet {
  3. @Override
  4. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. System.out.println("doGet");
  6. resp.getWriter().write("doGet");
  7. }
  8. @Override
  9. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  10. System.out.println("doPost");
  11. resp.getWriter().write("doPost");
  12. }
  13. @Override
  14. protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  15. System.out.println("doPut");
  16. resp.getWriter().write("doPut");
  17. }
  18. @Override
  19. protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  20. System.out.println("doGet");
  21. resp.getWriter().write("doDelete");
  22. }
  23. }

25e03e70f0664635b8262d25d340a02d.png_pic_center

可以看到此时只显示出了一个 doGet,那其他的请求怎么办呢?
这个时候就可以使用 postmanajax 来构造请求。

3 使用 postman 构造请求

输入路径,选择 GET 请求,点击 send 就构造好了一个 GET 请求,就出现了一个 doGet。

d8a53322fdcf487cb737b0a126684e25.png_pic_center

如果要构造其他的请求,可以更改即可。

d06430c75d7840ea8a8072dccd10c6bb.png_pic_center

4 使用 ajax 构造请求

在 webapp 目录下创建一个 .html 的文件。

绝对路径的写法:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>ajax</title>
  8. </head>
  9. <body>
  10. <script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
  11. <script>
  12. $.ajax({
  13. type: 'get',
  14. url: 'methods',
  15. success: function(body, status) {
  16. console.log(body);
  17. }
  18. });
  19. </script>
  20. </body>
  21. </html>

此处所写的 methods 就相当于是在 http://127.0.0.1:8080/Servlet 基础上再拼上一个 methods。
也就是 http://127.0.0.1:8080/Servlet/methods

f9b71b3aca924dde875d65bbe31e9027.png_pic_center

8a25b3c6c1cf4553a980ed4237d2b4f2.png_pic_center
在地址栏输入路径,打开控制台就可以看到构造好的 GET 请求了。

相对路径的写法:

  1. url: '/Servlet/methods',

接下来使用 ajax 构造 POST 请求。

  1. <body>
  2. <script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
  3. <script>
  4. $.ajax({
  5. type: 'post',
  6. url: 'methods',
  7. success: function(body, status) {
  8. console.log(body);
  9. }
  10. });
  11. </script>
  12. </body>
  13. </html>

163c8d90306e4db4bcd8582612896551.png_pic_center

Put、Delete…和上面的是一样的构造方法。

HttpServletRequest

Request 表示的是 HTTP 请求,HttpServlet 这个对象是 tomcat 自动创造的。
tomcat 其实会实现监听端口,接受连接,读取请求,解析请求,构造请求对象等一系列的工作。

核心方法

  • String getProtocol() 返回请求协议的名称和版本。
  • String getMethod() 返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT。
  • String getRequestURI()
    从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请
    求的 URL 的一部分。
  • String getContextPath() 返回指示请求上下文的请求 URI 部分。
  • String getQueryString() 返回包含在路径后的请求 URL 中的查询字符串。
  • Enumeration getParameterNames()
    返回一个 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

1 代码示例

  1. @WebServlet("/showRequest")
  2. public class ShowRequestServlet extends HttpServlet {
  3. @Override
  4. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. // 使用 StringBuilder 把这些 api 结果拼接起来,统一写响应中
  6. StringBuilder stringBuilder = new StringBuilder();
  7. stringBuilder.append(req.getProtocol());
  8. stringBuilder.append(req.getMethod());
  9. stringBuilder.append(req.getRequestURI());
  10. stringBuilder.append(req.getContextPath());
  11. stringBuilder.append(req.getQueryString());
  12. // 写回到响应中
  13. resp.getWriter().write(stringBuilder.toString());
  14. }
  15. }

cb112ae285064f8da99bd9d544f31592.png_pic_center

getProtocol 得到的是 HTTP/1.1、getMethod 得到的是 GET
getRequestURI 得到的是 /Servlet/showRequest、getContextPath 得到的是 /Servlet
getQueryString 得到的是 null

前端如何给后端传参

1 通过 GET 里的 query string 传参

在前端给后端传两个数字,一个是同学的 studentId,一个是 classId
发送一个 ?studentId=10&classId=20 这样的请求。

  1. @WebServlet("/getParameter")
  2. public class GetParameterServlet extends HttpServlet {
  3. @Override
  4. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. // 预期浏览器会发一个形如 /getParameter?studentId=10&classId=20 请求
  6. // 借出 req 里的 getParameter 方法就能拿到 query string 中的键值对内容了
  7. // getParameter 得到的是 string 类型的结果
  8. String studentId = req.getParameter("studentId");
  9. String classId = req.getParameter("classId");
  10. resp.setContentType("text/html");
  11. resp.getWriter().write("studentId = " + studentId + "classId = " + classId);
  12. }
  13. }

在地址栏中输入路径访问。
我这里的路径是 127.0.0.1:8080/Servlet/getParameter?studentId=10&classId=20

141a11c31bb5462db911a257412a27dd.png_pic_center

通过 getParameter 方法,?studentId=10&classId=20 这个键值对会自动被 tomcat 处理成
形如 Map 这样的结构,后续就可以直接通过 key 获取 value 了。

如果 key 在 query string 中不存在,此时返回的就是 null。

2 通过 POST 借助 form 表单传参

如果前端是 form 表单格式的数据,后端还是使用 getParameter 来获取。

这里的 form 表单格式的数据也是键值对,和 query string 的格式是一样的,只是这部分内容在 body 中。

  1. <form action="postParameter" method="post">
  2. <input type="text" name="studentId">
  3. <input type="text" name="classId">
  4. <input type="submit" value="提交">
  5. </form>

eae55c6fb987443db5c4517848faf401.png_pic_center
在输入框中输入以下的数据。

e0e10e1062364e0f859875253214f99a.png_pic_center

7d464e3af6c141f78ad915a41843fc0f.png_pic_center

当前显示 404 是因为 postParameter 还没有实现,但是不影响,打开 fiddler 抓包观察即可。

3fa2038a76a447aab9f92df08dcfa0e3.png_pic_center

当前代码中的 action 属性里的 postParameter 得到的就是抓包结果中的 postParameter 这个路径。
method 属性 得到的就是抓包结果中的 POST 。

input 标签里的 name 属性里的 studentId 和 classId 实现的就是最下方的 studentId=10&classId=20。
而在输入框中输入的内容及决定了它们两个的值。

使用 getParameter 既可以获取到 query string 中的键值对,也可以获取到 form 表单构造的 body 中的键值对。

  1. @WebServlet("/getparameter")
  2. public class PostGetParameterServlet extends HttpServlet {
  3. @Override
  4. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. String studentId = req.getParameter("studentId");
  6. String classId = req.getParameter("classId");
  7. resp.setContentType("text/html");
  8. resp.getWriter().write("studentId = " + studentId + "classId = " + classId);
  9. }
  10. }

抓包即可得到与之前相同的结果。

2d72693091374f6d9b40f7d9c8699a8a.png_pic_center

fa15adfd0fb14570995aaeec4dfb8ccc.png_pic_center

3 通过 json 格式传参

json 是一种非常主流的数据格式,也是键值对结构。

  1. {
  2. classId: 20,
  3. studentId: 10
  4. }

可以把 body 按照上面的格式来组织。

前段可以通过 ajax 的方式来构造出这个内容,也可以使用更简单的 postman 的方式。

dfca39f2bc524062be7a90b6105f9eca.png_pic_center

打开 postman,勾选上述的选项,之后点击发送即可,由于还没有实现 getparameter2 的代码,
所以此时发送后,会报一个 404 错误。

e09f315e29ba453c86c14c1baf7c982d.png_pic_center

接下来实现 getparameter2 的代码。

  1. @WebServlet("/getparameter2")
  2. public class PostParameter2 extends HttpServlet {
  3. @Override
  4. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. // 通过 getInputStream 把 req 对象里的 body 完整读取出来
  6. // 在流对象中读取多少个字节,取决于 Content-Length
  7. int length = req.getContentLength(); // 这是 body 实际的长度
  8. // 创建一个相同长度的字节数组
  9. byte[] buffer = new byte[length];
  10. // 借出 inputStream 来读取 body 的内容
  11. InputStream inputStream = req.getInputStream();
  12. inputStream.read(buffer);
  13. // 把这个字节数组转成 String,然后打印出来
  14. String body = new String(buffer, 0, length, "utf8");
  15. System.out.println("body =" + body);
  16. resp.getWriter().write(body);
  17. }
  18. }

通过 Content-Length 得到 body 的长度,然后再按照这个长度从请求对象中读取数据,
于是就把 body 给读取出来了。

213b1fa1c6f44a64952a75513dc6058e.png_pic_center

4de4eb2c8e9a418687217d59447ba3cd.png_pic_center

可以看到服务器与客户端都有了结果。

服务器这里打印的结果就是,从 请求 body 里读取的内容。

接下来使用 fiddler 抓包观察结果

df9c3543f35349aaae9f306ee88a3b1b.png_pic_center

理解它的流程

58610e78dabc4d6a8e2b0e85452e844c.png_pic_center

json 与 form 的区别:

json 格式代码的执行流程与上面通过 form表单传参的流程是类似的,只不过是传参的数据格式不同。

form 表单是形如 classId=20&studentId=10

json 格式则是形如:

  1. {
  2. classId: 20,
  3. studentId: 10
  4. }

当前通过 json 传递数据,服务器只是把整个 body 读出来了,但是没有按照键值对的的方式来处理,
也就是说,目前还不能根据 key 获取 value。

此时建议使用 第三方库 —— jackson

1、通过 https://releases.jquery.com/ 打开 maven 仓库

2、搜索 jackson,点击第一个

d4bb33e90af547b4a67a222a19f50373.png_pic_center

3、选择 2.14.1 版本

5e42b3f18f9a413b9ca2c0310ea50b88.png_pic_center

4、将 Maven 里的代码复制到 pom.xml 文件里的 dependency 标签里

63a4699c64af403b901e86e93b40df48.png_pic_center

5a0af7edc18141f095b41b930e44fe28.png_pic_center

接下来尝试更改代码的写法。

  1. class Student {
  2. public int studentId;
  3. public int classId;
  4. }
  5. @WebServlet("/getparameter2")
  6. public class PostParameter2 extends HttpServlet {
  7. @Override
  8. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  9. // 使用 jackson 涉及到的核心对象
  10. ObjectMapper objectMapper = new ObjectMapper();
  11. // readValue 可以把一个 jackson 格式的字符串转成 java 对象
  12. Student student = objectMapper.readValue(req.getInputStream(), Student.class);
  13. System.out.println(student.studentId + "," + student.classId);
  14. }
  15. }

Student student = objectMapper.readValue(req.getInputStream(), Student.class);

这条语句中的 readValue 方法,会执行下面的流程。

1、从 body 中读取 json 格式的字符串

  1. {
  2. classId: 20,
  3. studentId: 10
  4. }

2、根据第二个参数类对象,创建 Student 实例

3、解析上述的 json 格式的字符串,处理成 map 键值对结构

4、遍历所有的键值对,看键的名字和 Student 实例的哪个属性名字匹配,
就把对应的 value 设置到该属性中

5、返回该 Student 实例

启动服务器再点击发送,会在服务器上得到下面这样的结果。

3244faa046d0422b9fbd5b32fa3923d4.png_pic_center

HttpServletResponse

09e907f993d04ab48052f78fbbf63b46.png_pic_center

1 代码示例

1.1 设置字符集

如果想让下面的代码显示出中文,此时就需要设置字符集 为 utf-8

  1. @WebServlet("/getParameter")
  2. public class GetParameterServlet extends HttpServlet {
  3. @Override
  4. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. // 预期浏览器会发一个形如 /getParameter?studentId=10&classId=20 请求
  6. // 借出 req 里的 getParameter 方法就能拿到 query string 中的键值对内容了
  7. // getParameter 得到的是 string 类型的结果
  8. String studentId = req.getParameter("studentId");
  9. String classId = req.getParameter("classId");
  10. resp.setContentType("text/html");
  11. resp.getWriter().write("学生Id = " + studentId + "班级Id = " + classId);
  12. }
  13. }

22fd78f82bea451d98c44ab9135e7b74.png_pic_center

在设置之前会出现以上的结果。

接下来开始调用 setCharacterEncoding 设置字符集。

  1. resp.setCharacterEncoding("utf-8");

将上述的代码中添加上这条语句即可。

59aee9b1bc3041aba8d87f7521edd155.png_pic_center
刷新页面可以看到正确的显示出来了。

需要注意的是 设置字符集那条语句必须是在 getWriter().write() 的上面。
如果写在了下面是不会生效的。

也可以把字符集和 ContentType 一起设置

  1. resp.setContentType("text/html; charset = utf-8");

1.2 重定向

以 3 开头的状态码,浏览器会自动跳转到指定的新地址。

  1. @WebServlet("/redirectServlet")
  2. public class RedirectServlet extends HttpServlet {
  3. @Override
  4. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. resp.sendRedirect("https://www.sogou.com");
  6. }
  7. }

在地址栏中输入地址后,会跳转到指定的新地址。以上就是会跳转到搜狗的页面。

打开 fiddler 抓包并观察结果。

8df8899a32ae47fe87c8bbbe5718055e.png_pic_center

代码实现的第二种方式

  1. resp.setStatus(302);
  2. resp.setHeader("Location", "https://www.sogou.com/");

将代码修改为以上两句即可,这种写法是将上面的步骤给拆分成两步了。

下期来介绍服务器版本的表白墙案例!!!

1d9b5fd8cca3443da9ea82f2b90c536f.png_pic_center

发表评论

表情:
评论列表 (有 0 条评论,130人围观)

还没有评论,来说两句吧...

相关阅读