Servlet运行原理及常见API

一. Servlet运行原理

要知道我们所写的 Servlet 代码是没有 main 方法的, 那他是如何运行的呢? 其实是 Tomcat 在调用 Servlet, Tomcat 其实就是一个应用程序, 是运行在用户态上的一个普通的 Java 进程.
在这里插入图片描述

当浏览器给服务器发送请求的时候, Tomcat 作为 HTTP 服务器, 就可以接收到这个请求, 进而会调用 Serlvet API, 然后执行我们所写的 Servlet 程序来处理请求.
在这里插入图片描述

处理请求的过程中牵涉的不仅仅只有 HTTP, 还需要其他底层协议栈的支持, 但是处在应用层的我们并不会感知到其他层协议的细节, 我们只关注了应用层 HTTP 协议的相关操作, 这就是协议分层好处, 程序员在实现业务处理请求时, 不必去关心应用层下面的操作细节.
在这里插入图片描述

请求从浏览器到服务器, Tomcat 服务器构造响应, 再将响应写回浏览器, 这其中的详细交互过程如下:

  • 接收请求

用户在浏览器输入一个 URL, 此时浏览器就会构造一个 HTTP 请求, 这个请求会经过网络协议栈逐层进行封装成为二进制的bit流, 最终通过物理层硬件设备转换为光/电信号传输出去, 然后通过互联网上的一系列网络设备后服务器收到该信号后, 又通过网络协议栈逐层分用, 层层解析, 终还原成 HTTP 请求交给 Tomcat 进程进行处理 (根据端口号确定进程), Tomcat 通过 Socket 读取到这个请求 (一个字符串), 按照 HTTP 请求的格式来解析该请求, 根据 Context path 确定一个 webapp, 再通过 Servlet path 确定一个具体的类, 然后再根据 HTTP 请求的方法, 确定调用该类的哪个方法, 此时被调用方法中的 HttpServletRequest 中就包含该 HTTP 请求的详细信息.

  • 根据请求计算响应

doGet / doPost 方法中, 就可以写我们自己的响应逻辑, 这里会根据请求中的一 些信息, 来给 HttpServletResponse 对象设置一些属性, 例如状态码, header, body 等.

  • 返回响应

doGet / doPost执行完, Tomcat 会自动的把 HttpServletResponse 这个我们设置好的对象转换成一个 HTTP 协议的字符串, 层层封装后通过 Socket 将该响应发出去, 然后浏览器获取到 HTTP 响应, 浏览器的 Socket 读到该响应, 又会通过网络协议栈逐层分用解析, 按照 HTTP 响应的格式来解析该响应, 并把 body 中的数据按照一定的格式显示出来.

二. Servlet常用API

1. HttpServlet

写 Servlet 代码的时候, 首先第一步就是先创建类, 继承自 HttpServlet, 并重写其中的某些方法, 要注意HttpServlet 的实例只是在 Tomcat 启动时创建一次, 而不是每次收到 HTTP 请求都重新创建实例.

关键方法:

方法名称调用时机
init在 HttpServlet 实例化之后被调用一次
destory在 HttpServlet 实例不再使用的时候调用一次
service收到 HTTP 请求的时候调用
doGet收到 GET 请求的时候调用(由 service 方法调用)
doPost收到 POST 请求的时候调用(由 service 方法调用)
doPut/doDelete/doOptions/…收到其他请求的时候调用(由 service 方法调用)

1.1. Servlet的生命周期

其中 init, destory, service 这三个方法的调用时机, 就构成了 “Servlet” 的生命周期.

  1. init 方法, 在初始化阶段执行, 用来初始化每一个Servlet对象, 是在 Tomcat 首次收到 Servlet 类注解相关联路径的请求时, 就会调用执行, 用户可重写该方法, 来执行一些初始化程序的逻辑, 没有重写, init 方法一般是空的, 每个 Servlet 对象只执行一次.
  2. service 方法, 每次收到请求后, 就会执行, service中根据请求的类型不同, 调用不同的方法, doGet, doPost等, 会执行多次, 每收到一次 HTTP 请求就会执行一次.在这里插入图片描述
  3. destroy 方法, Tomcat 结束之前, 即 在 HttpServlet 实例销毁时就会执行该方法, 用来释放资源.

img

🎯代码示例:

我们写一个 Servlet 类继承 HttpServlet 并重写 init, service, destroy, doGet方法

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    public void init() throws ServletException {
        System.out.println("init被调用");
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("service被调用");
        super.service(req, resp);
    }

    @Override
    public void destroy() {
        System.out.println("destroy被调用");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //super.doGet(req, resp);

        // 响应数据显示到服务器控制台上观察
        System.out.println("hello get" + System.currentTimeMillis());
        // 将响应数据写回给浏览器
        resp.getWriter().write("hello get" + System.currentTimeMillis());
    }
}

🎯响应结果:

代码写好后启动 Tomcat 服务器, 在浏览器器中多次访问 http://127.0.0.1:8080/hello_servlet/hello, 然后观察执行结果.

img

img

可以看到, init 方法被调用了 1 次, sercice 方法被调用了 5 次, destroy 被调用了 1 次.

但要提醒的是, destroy 到底能否被执行到, 是不确定的, 如果是通过我们 IDEA 的停止按钮来关闭服务器, 这个本质操作是通过 Tomcat 的 8005 管理端口, 主动停止, 此时才会触发 destroy; 但如果是通过直接杀死进程的方式来关闭服务器, 此时就来不及执行 destroy了, 而这种暴力的关闭方式却是更方便更常用的, 所以不建议在 destroy 内执行有效代码.

1.2. Post请求的构造

对于 Get 请求, 我们可以使用 URL 结合查询字符串来进行构造, 但是 Post 请求不行, 需要我们使用 form 表单或者 ajax , 这里先使用 ajax 来进行构造
在 webapp 目录下创建一个 test.html 文件, 用来构造 Post 请求, 首先要引入 jquery 依赖, 我这里使用的是网络路径: https://cdn.bootcdn.net/ajax/libs/jquery/3.6.3/jquery.min.js, 然后调用 ajax 构造请求.

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.3/jquery.min.js"></script>
<script !src="">
    $.ajax({
        type: 'post',
        url: 'method',
        success: function (body, status) {
            console.log(body)
        }
    });
</script>

注意上面的写法中 URL 属性不能加 /, 加上表示的就是绝对路径了, 当然你要是写一个正确的绝对路径也是可以的, 还可以使用 ./ 来表示相对路径, 或者像上面一样直接不写表示相对路径, 基准路径就是这个 html 文件所在的路径.

下面是 Post 请求的处理逻辑, 要注意在 Servlet 类注解关联路径必须得加上 /, 且在同一 webapp 里面, 关联路径要唯一.

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/method")
public class MethodServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("Post请求");
        resp.getWriter().write("Post请求");
    }
}

启动 Tomcat, 在浏览器中访问 http://127.0.0.1:8080/hello_servlet/test.html 来看控制台输出的结果.

img
img
可以看到这里的结果与我们的预期是不一致的, 我们处理请求的时候返回的是 Post请求, 而这里打印的是 Post??, 出现这种情况的原因是发生了乱码, IDEA的默认编码格式为 utf-8, Windows 默认的编码格式是gbk, 那么浏览器根据系统在解析body的时候也是以 gbk 格式去进行解析, 所以这里需要我们去统一编码格式, 我们可以在 Servlet 程序里面设置字符格式, 设置方法为调用 HttpServletResponse 对象的setContentType方法, 传入参数text/html; charset=utf8, 或者是resp.setCharacterEncoding("utf8");, 这样就可以提前告知浏览器响应数据的编码格式是什么.

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/method")
public class MethodServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // resp.setCharacterEncoding("utf8");
        resp.setContentType("text/html; charset=utf8");
        System.out.println("Post请求");
        resp.getWriter().write("Post请求");
    }
}

重新启动服务器, 刷新页面:
在这里插入图片描述

同样的, 我们也可以在 Servlet 中写其他方法请求的处理逻辑, 重写对应方法即可.

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/method")
public class MethodServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf8");
        System.out.println("Get请求");
        resp.getWriter().write("Get请求");
    }

    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf8");
        System.out.println("Put请求");
        resp.getWriter().write("Put请求");
    }

    @Override
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf8");
        System.out.println("Delete请求");
        resp.getWriter().write("Delete");
    }
}

我们也可以使用postman这样的工具来进行请求的构造发送, 进行测试.

img

2. HttpServletRequest

当 Tomcat 通过 Socket API 读取一个 HTTP 请求 (字符串), 就会按照 HTTP 协议的格式把字符串解析成一个 HttpServletRequest 对象, 这个对象主要就是用于获取到请求的各个方面的信息, 尤其是前端传过来的自定义数据.

关键方法:

方法描述
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。query string中的键值对,键是允许重复的 (此时相当于一个key对应多个value) 这种用法很少见。
Enumeration getHeaderNames()返回一个枚举,包含在该请求中包含的所有的头名。
String getHeader(String name)以字符串形式返回指定的请求头的值。
String getCharacterEncoding()返回请求主体中使用的字符编码的名称。
String getContentType()返回请求主体的 MIME 类型,如果不知道类型则返回 null。
int getContentLength()以字节为单位返回请求主体的长度,并提供输入流,或者如果长度未知则返回 -1。
InputStream getInputStream()用于读取请求的 body 内容. 返回一个 InputStream 对象.

2.1. 获取请求信息

有了上面的 API, 我们就可以针对请求的信息进行获取, 就比如在浏览器中输入的URL为 http://127.0.0.1:8080/hello_servlet/showRequest?a=100&b=200, 这里构造的是包含查询字符串 (query string) 的一个 GET 请求, 通过 HttpServletRequest 类一系列的 API, 我们就可以获取到这个请求的方法类型, 协议版本, URL, 查询字符串, 请求头等信息, 其中头部信息的获取先要使用 getHeaderNames方法获取所有的头部信息的所有key值, 这s是一个枚举对象, 然后在根据 getHeader 方法通过key值遍历枚举对象获取value.

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;

@WebServlet("/showRequest")
public class ShowRequestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 这里是设置响应的 content-type.
        resp.setContentType("text/html; charset=utf-8");

        StringBuilder stringBuilder = new StringBuilder();
        //1. 协议的名称及版本
        stringBuilder.append("协议版本: ");
        stringBuilder.append(req.getProtocol());
        stringBuilder.append("<br>");
        //2. 方法类型
        stringBuilder.append("方法: ");
        stringBuilder.append(req.getMethod());
        stringBuilder.append("<br>");
        //3. URL
        stringBuilder.append("URL: ");
        stringBuilder.append(req.getRequestURI());
        stringBuilder.append("<br>");
        //4. context path
        stringBuilder.append("一级路径: ");
        stringBuilder.append(req.getContextPath());
        stringBuilder.append("<br>");
        //5. 查询字符串
        stringBuilder.append("查询字符串 :");
        stringBuilder.append(req.getQueryString());
        stringBuilder.append("<br>");

        //6. 获取到 header 中所有的键值对
        stringBuilder.append("<h3>获得头部的键值对: </h3>");
        Enumeration<String> headerNames = req.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            stringBuilder.append(headerName + ": " + req.getHeader(headerName));
            stringBuilder.append("<br>");
        }

        resp.getWriter().write(stringBuilder.toString());
    }
}

🎯响应结果:
在这里插入图片描述

2.2. 前端给后端传输数据的三种方式

前端给后端传输数据, 是非常常见的需求, 常见的有以下三种方式:

  1. 发送 Get 请求通过 query string 传输数据
  2. 发送 Post 请求通过 form 提交数据
  3. 发送 Post 请求通过 json 格式提交数据

接下来逐个介绍一下, 我们从前端给后端传参, 再将数据返回给前端.

2.2.1. 发送Get请求通过query string传输数据

我们约定, 前端通过URL中自定义 query string 传递 userName 和 passWord 这两个信息, 比如在浏览器中输入的URL为 http://127.0.0.1:8080/hello_servlet/getParameter/?userName=zhangsan&passWord=123456, 由于我们这里的 key 值是前后端交互前提前约定好的, 所以我们可以直接使用 getParameter 方法通 keyreq 中得到 value, 然后在后端我们就可以根据前端传来的数据构造响应返回给前端.

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/getParameter")
public class GetParameterServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String userName = req.getParameter("userName");
        String passWord = req.getParameter("passWord");
        resp.setContentType("text/html; charset=utf8");
        resp.getWriter().write("用户名 = " + userName + " 密码 = " + passWord);
    }
}

🎯响应结果:

请求中的 query string 键值对是通过字符串表示的, Tomcat 收到请求后就会把 URL 中的 query string 键值对转换成 Map 结构的数据, 以方便我们去查询, 需要注意的是我们这里通过 key 去获取到的 value 都是 String 类型的, 如果我们 getParameter 的参数前端并没有传递, 那么我们的 value 就是 null.

img

当然, 在不知道 query string 的 key 的情况下也是可以使用 getParameter 拿到查询字符串的各个键值对的, 可以先使用 getHeaderNames 方法获取所有的查询字符串的所有 key 值, 这个一个枚举对象, 然后再根据 getParameter 方法通过 key 值遍历枚举对象获取 value.

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Enumeration;

@WebServlet("/getParameter")
public class GetParameterServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");
        StringBuilder stringBuilder = new StringBuilder();

        Enumeration query = req.getParameterNames();
        while(query.hasMoreElements()) {
            String key = (String)query.nextElement();
            stringBuilder.append(key + ": " + req.getParameter(key));
            stringBuilder.append("<br>");
        }
    }
}

🎯响应结果:

img

2.2.2. 发送Post请求通过form提交数据

使用 Post 请求来传递数据, 数据此时是在 HTTP 格式中的 body 部分的, 使用 from 表单进行构造, 此时 body 中的请求内容的格式 (Content-Type) 是 application/x-www-form-urlencode 格式, 在形式上和 query string 是一样的, 后端仍然使用 getParameter 来获取.

我们还在 webapp 目录下的 test.html 文件中构造一个 from 表单

img

<form action="postParameter" method="post">
    <span>userId</span>
    <input type="text" name="studentId">
    <span>classId</span>
    <input type="text" name="classId">
    <input type="submit" value="提交">
</form>

Servlet程序接收和处理请求:
对于 x-www-form-urlencode格式 请求可以直接使用 HttpServletRequest 中的 getParameter 方法依据 key 来获取 value, 然后再将获取到的数据返回, form表单构造的请求会自动跳转页面.

这里的代码要注意, 需要设置拿到请求的编码格式, 显式的告诉后端代码, 请求使用的是 utf8 编码, 要不然Tomcat 服务器在进行 urldecode 解码时可能会由于编码问题解析错误, 尤其是请求内容中有中文存在的情况下, 也就是说, 我们在写后端代码时, 最好将请求和响应的编码格式都进行设置, 保证前后端解析的统一.

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/postParameter")
public class PostParameterFromServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置请求与响应编码格式
        req.setCharacterEncoding("utf-8");
        resp.setContentType("text/html; charset=utf8");

        StringBuilder stringBuilder = new StringBuilder();
        String studentId = req.getParameter("studentId");
        String classId = req.getParameter("classId");
        // 1. 请求正文
        stringBuilder.append("请求正文: ");
        stringBuilder.append("studentId = " + studentId + " classId = " + classId);
        stringBuilder.append("<br>");

        //2. 正文编码格式
        stringBuilder.append("body编码格式: ");
        stringBuilder.append(req.getCharacterEncoding());
        stringBuilder.append("<br>");
        //3. mine
        stringBuilder.append("mine: ");
        stringBuilder.append(req.getContentType());
        stringBuilder.append("<br>");
        //4. 正文长度
        stringBuilder.append("body长度: ");
        stringBuilder.append(req.getContentLength());
        stringBuilder.append("<br>");

        resp.getWriter().write(stringBuilder.toString());
    }
}

先来抓个包看一下请求数据:
在这里插入图片描述

🎯响应结果:

img

2.2.3. 发送Post请求通过json格式提交数据

使用 Post 请求传输数据, 还可以使用当前比较主流的 json 数据格式组织 body 中的数据, 它也是键值对格式的.

json格式:

[
    { 
        key1 : value1-1 ,
        key2:value1-2 
    }, 
    { 
        key1 : value1-1 ,
        key2:value1-2 
    }, 
    ...
    { 
        key1 : value1-1 ,
        key2:value1-2 
    }
]

对于 json 格式, Servlet 自身是没有内置 json 的解析功能的, 如果我们自己进行手动解析并不容易, 我们这里就直接把 body 中的内容完整的读取出来, 然后返回给客户端.

首先实现一下后端逻辑:

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;

@WebServlet("/getJsonPost")
public class PostParameterJsonServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 直接把 req 对象里 body 完整的读取出来.
        // getInputStream
        // 在流对象中读多少个字节取决于 Content-Length
        int length = req.getContentLength();
        byte[] buffer = new byte[length];
        //获取流对象
        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 = " + body);
    }
}

前端构造请求的部分既可以使用 ajax 的方式构造, 也可以使用postman直接构造测试, 这里先就使用postman演示了.

img

来抓包看一下请求数据:

img

🎯响应结果:
在这里插入图片描述

更方便的, 我们可以借助第三方库来解析处理 json, 比如使用 Jackson, Jackson依赖导入过程如下:

img

img
img
img

首先在前端 js 代码中构造出格式为 json 格式的请求, 使用 ajax 构造 post 请求, 使用 contentType 来说明请求的类型, data 属性来设置 body 的内容.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

    <input type="text" id="studentId">
    <input type="text" id="classId">
    <input type="button" id="submit" value="提交">
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.3/jquery.min.js"></script>

    <script>
        let studentIdInput = document.querySelector("#studentId");
        let classIdInput = document.querySelector("#classId");
        let button = document.querySelector("#submit");

        button.onclick = function() {
            $.ajax({
                type : "post",
                url: "getJsonPost",
                contentType: "appliaction/json",
                data:JSON.stringify({
                    studentId: studentIdInput.value,
                    classId:classIdInput.value
                }),
                success: function(body){
                    console.log(body);
                }
            })
        }
    </script>
        </body>
</html>

然后在 java 后端代码中使用 Jackson 处理 请求内容.

  1. 创建 Jackson 核心对象 ObjectMapper 对象.
  2. 创建用来接受 json 数据的实体类.
  3. 读取请求中的 body 信息, 该过程通过 ObjectMapper 对象的readValue方法实现, 这个方法的参数有两个, 第一个参数用来表示请求的来源, 可以是路径字符串, 也可以是InputSream对象, 也可以是File对象, 第二个参数表示接收 json 数据的实体类对象.
  4. 处理并响应请求.
import com.fasterxml.jackson.databind.ObjectMapper;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

class Student {
    public int studentId;
    public int classId;
}

@WebServlet("/getJsonPost")
public class PostParameterJsonServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 设置响应格式
        resp.setContentType("text/html; charset=utf8");
        
        // 将 body 中的内容按照键值对的格式解析
        // 使用 jackson 涉及到的核心对象ObjectMapper对象的readValue方法来解析
        ObjectMapper objectMapper = new ObjectMapper();
        
        // readValue 把一个 json 格式的字符串转成 Java 对象.
        // 第一个参数可以是路径字符串可以是输入流对象, 也可以是File对象
        // 第二个参数, 表示需要将请求的json格式数据转换成哪一个java对象
        Student student = objectMapper.readValue(req.getInputStream(), Student.class);
        System.out.println(student.studentId + ", " + student.classId);
        
        //写回响应
        resp.getWriter().write(student.studentId + ", " + student.classId);
    }
}

🎯响应结果:

img
img

readValue 方法基本原理:

  1. 读取 body 中 json 格式的数据字符串, 并解析成若干键值对.
  2. 根据第二个参数实体类对象, 创建 Student 实例.
  3. 遍历解析出来的键值对, 获得 key, 并与所需传入的对象中的属性相比, 如果 key 与属性的名字相同, 则把 key 对应的 value赋值给这个属性(通过反射完成).
  4. 返回该 Student 对象.

3. HttpServletResponse

这个类构造的就是一个 HTTP 响应.

关键方法:

方法描述
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 中写入二进制格式数据.

3.1. 设置响应状态码

设置方法很简单, 只需要调用 httpServletResponse 对象中的 setStatus 方法就可以了, 设置不同的状态码, 只要变换 status 的值即可, 就可以看到不同的响应结果.

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/status")
public class StatusServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf8");
        //设置状态码
        int status = 404;
        resp.setStatus(status);
        resp.getWriter().write("<h1>404 没找到</h1>");
    }
}

启动服务器, 响应结果如下:
在这里插入图片描述

还是抓个包看一下这里的响应:
在这里插入图片描述

也就是说, 平时我们所见到的其他的网站的 404 都是人家自定义的 404 状态响应页面, 我们可以设置返回 tomcat 自带的错误页面.

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/status")
public class StatusServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 返回 tomcat 自带的错误页面
        resp.sendError(404);
    }
}

重启服务器, 刷新页面响应结果如下:

img

3.2. 自动页面刷新

自动页面刷新只要在响应报头 (header) 中设置一下 Refresh 字段就能实现页面的定时刷新了, 对于响应 header 的设置, 可以通过 HttpServletResponse 对象中的 setHeader 方法来设置 Refresh 属性和刷新频率.

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/autorefresh")
public class AutoRefreshServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset = utf8");
        //设置Refresh, 第二个参数表示刷新频率, 单位是秒, 每隔 1s 刷新一次
        resp.setHeader("Refresh", "1");
        //响应                                获取毫秒级别的时间戳
        resp.getWriter().write("时间戳:" + System.currentTimeMillis());
    }
}

抓包看一下响应参数:

img

🎯响应结果:
在这里插入图片描述

还可以对这里的时间戳进行格式化输出

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.SimpleDateFormat;

@WebServlet("/autorefresh")
public class AutoRefreshServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset = utf8");
        //设置Refresh, 第二个参数表示刷新频率, 单位是秒, 每隔 1s 刷新一次
        resp.setHeader("Refresh", "1");
        //响应                                获取毫秒级别的时间戳
        // resp.getWriter().write("时间戳:" + System.currentTimeMillis());
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        resp.getWriter().write("格式化时间戳 = " + format.format(System.currentTimeMillis()));
    }
}

🎯响应结果:

img

3.3. 重定向

就是实现一个程序, 返回一个重定向 HTTP 响应, 即自动跳转到另外一个页面.

第一步, 设置状态码为302.
第二步, 设置响应 header的 Location 字段, 即调用 setHeader 方法, 第一个参数填 Location, 表示设置 的 header 字段为 Location, 第二个参数为重定向的目的地址, 你要重定向到哪一个网址就传入相应的域名即可.

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/redirect")
public class RedirectServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // resp.sendRedirect("https://www.bilibili.com/");
        resp.setStatus(302);
        resp.setHeader("Location", "https://www.bilibili.com/");
    }
}

抓个包看一下响应参数:

img

🎯响应结果:

img
Servlet中还提供了更为简洁的重定向方法, 使用 HttpServletResponse 类中的 sendRedirect 方法, 参数传入重定向的域名即可, 效果与上面是一样的.

  • 82
    点赞
  • 75
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 76
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 76
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

韵秋梧桐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值