Servlet运行原理及常见API

news2024/11/28 5:37:55

文章目录

  • 一. Servlet运行原理
  • 二. Servlet常用API
    • 1. HttpServlet
      • 1.1. Servlet的生命周期
      • 1.2. Post请求的构造
    • 2. HttpServletRequest
      • 2.1. 获取请求信息
      • 2.2. 前端给后端传输数据的三种方式
        • 2.2.1. 发送Get请求通过query string传输数据
        • 2.2.2. 发送Post请求通过form提交数据
        • 2.2.3. 发送Post请求通过json格式提交数据
    • 3. HttpServletResponse
      • 3.1. 设置响应状态码
      • 3.2. 自动页面刷新
      • 3.3. 重定向

一. 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 方法, 参数传入重定向的域名即可, 效果与上面是一样的.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/626805.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python爬虫基础知识点

Python爬虫是使用Python编写的程序&#xff0c;可以自动抓取互联网上的数据。常用的Python爬虫框架包括Scrapy、BeautifulSoup、Requests等。Python爬虫可以应用于众多场合&#xff0c;如大数据分析、信息监测、数据挖掘和机器学习等领域。那么新手应该如何学习python爬虫呢&am…

LLCC68+HC32L110

一、准备 最近研究一下LORA&#xff0c;因为LLCC68是阉割版本的SX126X&#xff0c;所以就选用这颗芯片了 模块&#xff1a;随便选一家都可以我简单做了个测试板&#xff0c;接口如下&#xff1a;只需要注意DIO配置成中断引脚即可 二、程序流程 RX TX初始化 #define RF_FREQ…

springboot openfeign Sentinel统一降级处理

背景 openfeign降级常规操作如下: 此种方式太过于麻烦&#xff0c;每一个方法都要写一个降级逻辑&#xff0c;并且降级逻辑大多是雷同的。 目标 提供默认的降级方式&#xff0c;若openfeign未指定FallbackFactory则走默认降级方式&#xff0c;否则就走自定义的FallbackFact…

C++【unordered_map/set的底层实现-哈希表】—含有源代码

文章目录 前言一、unordered_map/unordered_set容器&#xff08;1&#xff09;unordered_map容器介绍及使用&#xff08;2&#xff09;unordered_set容器介绍及使用&#xff08;3&#xff09;它们和map/set对比 二、容器底层结构&#xff08;1&#xff09;哈希表概念&#xff0…

分布式算法之一致性 Hash 算法

tip&#xff1a;作为程序员一定学习编程之道&#xff0c;一定要对代码的编写有追求&#xff0c;不能实现就完事了。我们应该让自己写的代码更加优雅&#xff0c;即使这会费时费力。 &#x1f495;&#x1f495; 推荐&#xff1a;体系化学习Java&#xff08;Java面试专题&#…

第35步 机器学习实战DLC:不平衡数据处理(下)

失踪人口回归的第二期&#xff0c;继续说一说用PSM处理不平衡数据。 一、啥叫PSM PSM全称为Propensity Score Matching&#xff0c;翻译过来就是倾向匹配得分&#xff0c;为了省流&#xff0c;让小Chart介绍一下&#xff1a; 放到我们的数据就是&#xff1a;根据某个特征&…

架构师需要看透公司的用户心智定位

一个架构师要站在用户的角度去思考架构的规划和设计。把注意力放在用户身上&#xff0c;已经有无数案例证明是可以带来重大商业和技术突破的。 很多创业公司&#xff0c;从初创到倒闭&#xff0c;都没搞清楚自己的目标人群和心智。如果一个公司&#xff0c;能锁定目标人群及其…

7个最佳WooCommerce跨境电商社交媒体插件

在互联的早期&#xff0c;您通常可以将产品直接放到网上并出售。但在今天你必须进行营销和做广告。如果没有包括社交媒体在内的可持续营销策略&#xff0c;您的商店可能会被忽视。值得高兴的是&#xff0c;有很多 WooCommerce跨境电商社交媒体插件可以用来传播信息&#xff0c;…

【CMake 入门与进阶(5)】 CMakeLists.txt 语法规则基础及部分常用指令-续(附使用代码)

project project命令用于设置工程名称&#xff1a; # 设置工程名称为 HELLO project(HELLO)执行这个之后会引入两个变量&#xff1a;HELLO_SOURCE_DIR 和 HELLO_BINARY_DIR&#xff0c;注意这两个变量名的前缀就是工程名称&#xff0c;HELLO_SOURCE_DIR 变量指的是 HELLO 工程…

华为推出首款全栈自主数据库 GaussDB,你怎么看?

鸿蒙套壳&#xff0c;鸿蒙套壳完了ERP套&#xff0c;ERP套壳&#xff0c;ERP套壳完了数据库套壳&#xff0c;数据库套壳完了…… 犹记得GaussDB之前一直宣传是基于PostgreSQL研发而来&#xff0c;不知道今天为啥摇身一变为首款全栈自主分布式数据库了。 基于开源研发改不恶心。…

智能设备管理系统

传统设备实施管理难点&#xff1a; 1、日常工作繁琐&#xff0c;手动纸质记录和 Excel 管理设备数据麻烦&#xff0c;后期难以汇总管理&#xff0c;且数据易丢失。 2、需核对设备巡检、保养、故障维修记录&#xff0c;手动更新设备状态和最近维修时间等。 3、无法实时获取设备最…

大学生网络工程想走网络安全方向该怎么规划?

明确需求,确定方向 网络安全 网络安全 是一个很广的概念&#xff0c;涉及的岗位也是非常多的&#xff0c;有安全服务、安全运维、渗透测试、web安全、安全开发、安全售前等等。可以看看下面每个岗位的要求与自身兴趣能力匹配度再决定最适合自己的方向。 渗透测试/Web安全工程师…

开启人机协作新时代:协作机器人的应用与展望

原创 | 文 BFT机器人 01 蓄势待发&#xff0c;产业变革新引擎 近年来&#xff0c;在政策扶持、资本助推和技术创新的共同作用下&#xff0c;产业迎来发展黄金期。日前&#xff0c;各行各业正经历产业智能化转型&#xff0c;机器人市场规模不断扩大&#xff0c;发展前景广阔&…

万宾建筑结构健康监测系统方案

建筑结构健康监测是现代建筑工程领域的重要措施之一。通过实时监测和评估建筑物的结构状态&#xff0c;可以及早发现潜在的问题&#xff0c;保障建筑物的安全性和稳定性。 随着城市化进程的加速和建筑规模的扩大&#xff0c;建筑结构的安全性和稳定性越来越受到关注。然而&…

chatgpt赋能python:Python快速打开:如何提高Python执行速度

Python 快速打开&#xff1a;如何提高 Python 执行速度 介绍 Python 是一种解释型语言&#xff0c;由于其简洁易读&#xff0c;广泛用于数据科学、机器学习、Web 开发等领域。然而&#xff0c;它的执行速度相对较慢&#xff0c;这通常是由于其解释器中面临的硬件资源限制以及…

如何查看docker下的mysql版本

进入运行的mysql的容器 docker exec -it mysqlserver bash 标红的位置可以是我们运行的别名&#xff0c;也可以为id 我们想连接mysql服务时报错了 我们看下配置文件 cat /etc/my.cnf 可以看到配置文件的sock文件位置并不在/var/lib/mysql文件夹中 这里又两种办法 1.直接修改…

JVM--方法区元空间

前言 本篇对java的JVM线程共享内存中的方法区进行系统性的讲解。 1、方法区&元空间概念 方法区是《Java虚拟机规范》中规定的一个内存区域&#xff0c;它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。 元空间是方法区的实现。方法…

python基础----09-----类、对象、魔法方法、封装、继承、类型注解、多态、数据分析案例

一 初识对象 说白了就是类的实例化&#xff0c;类是一个抽象层的定义。 例如下面class Student就是定义的一个类&#xff0c;它是抽象层&#xff0c;然后stu_1 Student()&#xff0c;我们根据类创建了一个对象&#xff0c;就是对类的实例化&#xff0c;这个实例化对象我们是可…

paas云底座-数字化转型,你真的了解么

云底座是什么&#xff1f; 如图 底座其实就是一个基础打个比方&#xff1a; 把底座看成一块土地&#xff0c;我们在这块土地上可以盖楼房&#xff0c;可以挖一个游泳池&#xff0c;可以开一家饭店 也就是说我们这块土地可以开发很多东西言归正传 云底座其实就是数字化转型的“底…

【快速幂】-迭代法:详解

何为快速幂&#xff1f; 我们经常会计算&#xff1a;。STL中有自带的pow函数&#xff0c;如果当n很大的时候&#xff0c;那么一定会TLE。 因此&#xff0c;我们需要另一种求值的方法&#xff1a;快速幂&#xff01; 快速幂有两种做法&#xff1a;1&#xff1a;递归 2…