【JavaWeb】Servlet API 详解——HttpServlet

具体研究一下 Servlet 里面的关键APl~~
主要介绍这三个类:

  • HttpServlet
  • HttpServletRequest
  • HttpServletResponse

一、HttpServlet

1、多态

咱们自己写的代码,就是通过继承这个类,重写其中的方法,来被 Tomcat 执行到的

也就是多态! 举例:

集合类:

List<String> list = new ArrayList<>();
list.add(.......

多线程:

class MyThread extends Thread {
    
    
    public void run() {
    
    
    }
}

Servlet 这里也是一个很好的例子~~

        // 调用 Servlet 对象的 service 方法
        // 这里就会最终调用到我们自己写的 HttpServlet 的子类里的方法了
        try {
    
    
            ins.service(req, resp); // 4)
        } catch (Exception e) {
    
    
            // 返回 500 页面,表示服务器内部错误
        }
class Servlet {
    
    
    public void service(HttpServletRequest req, HttpServletResponse resp) {
    
    
        String method = req.getMethod();
        if (method.equals("GET")) {
    
    
            doGet(req, resp); // 调用的是我们自己重写的
        } else if (method.equals("POST")) {
    
    
            doPost(req, resp);
        } else if (method.equals("PUT")) {
    
    
            doPut(req, resp);
        } else if (method.equals("DELETE")) {
    
    
            doDelete(req, resp);
        }
        ......
    }
}

2、核心方法

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

我们实际开发的时候主要重写 doXXX 方法,很少会重写 init / destory / service

这些方法的调用时机,就称为 Servlet 生命周期 (也就是描述了一个 Servlet 实例从生到死的过程)

注意: HttpServlet 的实例只是在程序启动时创建一次,而不是每次收到 HTTP 请求都重新创建实例


3、代码示例:处理 POST 请求

创建 MethodServlet 类

当我们给两个类都指定了 /hello 路径的时候,Tomcat 自己就退出了~~

结果:Process finished with exit code 0

进程的退出码~~
C 语言 的 main 函数的 return值
一般退出码用 0 表示是正常退出,其他非 0 值表示异常退出 (直接杀死 tomcat,此时退出码是个 -1 )

在这里插入图片描述

人家明确的告诉你,这俩类,不能映射为同一个 url-pattern

@WebServlet("/method") // 在同一个 webapp 里 多个 Servlet 关联的路径 不能相同!
public class MethodServlet extends HttpServlet {
    
    
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        // super.doPost(req, resp);
        resp.getWriter().write("POST 响应");
    }
}

如果是 GET 请求,直接浏览器中,通过 URL 就能构造

构造 POST 请求:

  1. form
  2. ajax

在这里插入图片描述

<!-- jquery cdn -->
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script>
    $.ajax({
      
      
        type:'post',
        url: 'method',
        success: function(body) {
      
      
            console.log(body);
        }
    });
</script>

在这里插入图片描述

启动,观察响应结果:

在这里插入图片描述

我们预期的响应是:“POST 响应”,但是出现了乱码!!!
在当前场景中,字符串主要有两个环节:

  • 生成 (IDEA 里面通过硬编码的方式把这个字符串写进去)

  • 展示 (浏览器把这个字符串显示到控制台里)

如果这俩操作,不能按照统一的编码方式来进行就容易出现乱码~~

上面我们写的 post 方法:
这里的编码,就取决于,IDEA 当前是以怎样的编码来组织咱们的项目,一般默认都是 utf8~~ settings-搜索 encoding 可以看到

浏览器展示字符串:
默认情况下,一般是跟随着系统的默认编码~~ (windows 简体中文版,一般默认编码是 gbk ),显式地告诉浏览器,你不要按照 gbk 的方式来读了,要按照 utf8 来读!!

解决方法 :在 post 方法中,加上一句:

resp.setContentType("text/html; charset=utf8");

二、Postman

是否有更简单的,不用写代码,就能构造 POST 请求的方式呢?
有一系列的第三方工具,可以构造任意的 HTTP 请求,其中一个比较知名的,就是 postman
postman 最初是 chrome 的一个插件,后来不小心就火了,现在单独搞了一个 postman 程序,同时还有了个对象! (postwoman)

在这里插入图片描述


三、HttpServletRequest

  • 当 Tomcat 通过 Socket API 读取 HTTP 请求(字符串),并且按照 HTTP 协议的格式把字符串解析成 HttpServletRequest 对象
  • HttpServletRequest 对应到一个 HTTP 请求,HTTP请求中有啥,这里就有啥
    HttpServletResponse 对应到一个HTTP响应,HTTP响应中有啥,这里就有啥

1、核心方法

通过这些方法可以获取到一个请求中的各个方面的信息
注意:请求对象是服务器收到的内容,不应该修改,因此上面的方法也都只是 “读” 方法,而不是 "写"方法

方法 描述
String getProtocol() 返回请求协议的名称和版本。
String getMethod() 返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT。
String getRequestURI() 从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该 请求的 URL 的一部分。
String getContextPath() 返回指示请求上下文的请求 URI 部分。
String getQueryString() 返回包含在路径后的请求 URL 中的查询字符串
Enumeration getParameterNames() 返回一个 String 对象的枚举,包含在该请求中包含的参数的名 称。
String getParameterNames() 以字符串形式返回请求参数的值,或者如果参数不存在则返回 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 对象

getRequestURI() 方法名字叫做 URI 不是 URL,这其实是两个概念,但是这俩概念非常相似,甚至可以混用

getQueryString 得到是完整的查询字符串,形如 ?a=10&b=20。下面两个方法,相当于是把查询字符串给解析成键值对了,
getParameterNames 是得到所有的 key,以 Enum 的方式来表示,getParameter 则是根据 key 来拿到 value

getHeaderNamesgetHeader 获取请求报头,请求报头也是键值对结构,此处 servlet 也是把请求头进行了解析,得到了键值对结构

getInputStream 这个就得到了一个输入流对象,从这个对象中读取数据,其实就读到了请求的 body


1.1、代码示例: 打印请求信息

创建 ShowRequestServlet 类

@WebServlet("/showRequest")
public class ShowRequestServlet extends HttpServlet {
    
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        // super.doGet(req, resp);
        // 调用刚才涉及的几个关键 API,把结果组织到一个 html 中,并作为响应的 body
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("<h3>首行部分</h3>");
        stringBuilder.append(req.getProtocol());
        stringBuilder.append("<br>");
        stringBuilder.append(req.getMethod());
        stringBuilder.append("<br>");
        stringBuilder.append(req.getContextPath());
        stringBuilder.append("<br>");
        stringBuilder.append(req.getQueryString());
        stringBuilder.append("<br>");
        stringBuilder.append("<h3>header 部分</h3>");
        Enumeration<String> headerNames = req.getHeaderNames();
        while (headerNames.hasMoreElements()) {
    
    
            String headerName = headerNames.nextElement(); // 请求报头 键值对
            String headerValue = req.getHeader((headerName));
            stringBuilder.append(headerName + ": " + headerValue + "<br>");
        }

        resp.setContentType("text/html; charset=utf8 // 防止乱码
        resp.getWriter().write(stringBuilder.toString());
    }
}

访问 http://127.0.0.1:8080/hello102/showRequest

结果:

在这里插入图片描述

改成 http://127.0.0.1:8080/hello102/showRequest?a=10,queryString 就不再是 null,而是 a=10

  • 每次咱们修改了代码之后,都需要重新打包部署,才能生效
    是否存在,只要代码一修改,就自动重新打包部署的机制?? 不就又省事了嘛??
  • 有的!!! 程序猿这种生物,对于 “重复性” 的操作,是深恶痛绝的!! —— 热加载

2、获取 GET 请求中的参数

上述 API 能够让我们拿到 HTTP 请求的各个方面内容~~ 但是却没那么常用
更常用的,其实是 getParameter 这个方法 (取到query string中的详细内容)

  • GET 请求中的参数一般都是通过 query string 传递给服务器的,形如

  • https://v.bitedu.vip/personInf/student?userId=1111&classId=100

  • 此时浏览器通过 query string 给服务器传递了两个参数, userId 和 classId, 值分别是 1111 和 100

  • 在服务器端就可以通过 getParameter 来获取到参数的值

创建 GetParameterServlet 类

@WebServlet("/getParameter")
public class GetParameterServlet extends HttpServlet {
    
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        // super.doGet(req, resp);
        // 预期浏览器传来的请求:/getParameter?userId=123&classId=789
        String userId = req.getParameter("userId");
        String classId = req.getParameter("classId");
        resp.getWriter().write("userId=" + userId + ", classId=" + classId);
    }
}

当没有 query string 的时候,getParameter 获取的值为 null

  • 访问: http://127.0.0.1:8080/hello102/getParameter

    结果:userId=null, classId=null

  • 访问:http://127.0.0.1:8080/hello102/getParameter?userId=10&&classId=20

    结果:userId=10, classId=20


3、获取 POST 请求中的参数

POST 请求的参数一般通过 body 传递给服务器,body 中的数据格式有很多种

POST 请求 body 格式:

  1. x-www-form-urlencoded
  2. form-data
  3. json

3.1、POST 请求中 body 按照 form 表单的形式

如果是采用 form 表单的形式,服务器如何获取参数呢? 仍然可以和 GET 一样通过 getParameter 获取参数的值,

创建 PostGetParameterServlet 类

@WebServlet("/postGetParameter")
public class PostGetParameterServlet extends HttpServlet {
    
    
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        // super.doPost(req, resp);
        // 假设前端传过来的参数是:userId=10&classId=20
        // 服务器也是通过 req.getParameter 来获取内容的
        String userId = req.getParameter("userId");
        String classId = req.getParameter("classId");
        resp.getWriter().write("userId=" + userId + ", classId=" + classId);
    }
}

构造 POST 请求:

注意: test.html 是在 webapp 目录下,和 WEB-INF 是在同级目录中,而不是在 WEB-INF 里面
如果你把 test.html 放错位置了,那么大概率就要 404~

在这里插入图片描述

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

<!-- jquery cdn -->
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script>

</script>

访问:http://127.0.0.1:8080/hello102/test.html

在这里插入图片描述

结果: userId=111, classId=222

此时通过抓包可以看到,form 表单构造的 body 数据的格式为:

POST http://127.0.0.1:8080/hello102/postGetParameter HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Content-Length: 22
Cache-Control: max-age=0
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="100", "Google Chrome";v="100"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: http://127.0.0.1:8080
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:8080/hello102/test.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

userId=111&classId=222

3.2、POST 请求中 body 按照 JSON 的格式

{
    
    
	userld: 1234,
    classld: 5678
}

对于这种 body 为 json 的格式来说,如果手动来解析,其实并不容易 ( JSON 里面的字段是能嵌套的~)
像这种情况,手动处理比较麻烦,可以使用第三方的库,来直接处理 json 格式数据~~

Java 生态中,用来处理 JSON 的第三库,种类也是很多~~
我们使用的库,叫做 Jackson (Spring官方推荐的库)

通过 maven 把 jackson 这个库,给下载到本地,并引入到项目中(还是 pom.xml 标签 dependencies 中)~~

在这里插入图片描述

在这里插入图片描述

1). 在浏览器前端代码中,通过 js 构造出 body 为 json 格式的请求

<!-- 要想构造一个 json 格式的请求, 就不再使用 form 而是使用 ajax 了 -->
<input type="text" id="userId">
<input type="text" id="classId">
<input type="button" value="提交" id="submit">

<!-- jquery cdn -->
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script>
    let userIdInput = document.querySelector("#userId");
    let classIdInput = document.querySelector("#classId");
    let buttom = document.querySelector("#submit");
    buttom.onclick = function() {
    
    
        $.ajax({
    
    
            type: 'post',
            url: 'postJson',
            contentType: 'application/json',
            data: JSON.stringify({
    
    
                userId: userIdInput.value,
                classId: classIdInput.value
            }),
            success: function(body) {
    
    
                console.log(body);
            }
        });
    }
</script>

对于像 post 这样的请求,ajax 就允许使用 data 属性来构造请求的 body 部分
此处要构造的内容,其实是一个 js 对象

需要把这个 js 的对象,再通过 JSON.stringify ,从 js 对象,转成一个字符串
这个字符串的格式就正是 json 的 格式

—— {“userld”:“123”,“classld”:“456”}

2). 在 java 后端代码中,通过 jackson 来进行处理

需要使用 jackson,把请求 body 中的数据读取出来,并且解析成 Java 中的对象

User user = objectMapper.readValue(req.getInputStream(), User.class);

readValue()把 JSON 格式的字符串,转成 Java 的对象

  • 第一个参数: 表示对哪个字符串进行转换,这个参数可以填写成一个 String / 一个 InputStream 对象 / 一个 File 对象
  • 第二个参数: 表示要把这个 JSON 格式的字符串,转成哪个 Java 对象

上面我们说 Json 的格式形如:—— {“userld”:“123”,“classld”:“456”}
通过 getInputStream() 得到到就是这个字符串

那么 readValue 是如何转换的?

  1. 先把 getInputStream 对应的流对象里面的数据都读取出来

  2. 针对这个 json 字符串进行解析,从 字符串 => 键值对

    • key: userld; value: 123

    • key: classld; value: 456

    • 此处就要求,User 类 的属性名,得和键值对中的 key 的名字匹配~~
      (要求名字匹配,这个只是 jackson 默认行为),如果你就非得想搞个不匹配的名字,也不是不可以

  3. 遍历这个键值对,依次获取到每一个 key
    根据这个 key 的名字,和 User 类里面的属性名字,对比一下~~ 看有没有匹配的名字!!

    • 如果发现匹配的属性,则把当前 key 对应的 value 赋值到该 User 类的属性中~~ (赋值的过程中同时会进行类型转换)
    • 如果没有匹配的属性,就跳过,取下一个 key ~~
  4. 当把所有的键值对都遍历过之后,此时 User 对象就被构造的差不多了~~

class User {
    
    
    // 当前都设为 public,如果是 private,但是同时提供了 getter setter,效果等同
    public int userId;
    public int classId;
}

@WebServlet("/postJson")
public class PostJsonServlet extends HttpServlet {
    
    
    // 1、创建一个 Jackson 的核心对象
    ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
    
    
        // 2、读取 body 中的请求,然后使用 ObjectMapper 来解析成需要的对象
        User user = objectMapper.readValue(req.getInputStream(), User.class);
        // 源代码是.java => 二进制字节码.class => 被加载到内存中就成为了 class 对象
        // getInputStream():读取请求的 body 内容. 返回一个 InputStream 对象
        resp.getWriter().write("userId: " + user.userId + ", classId: " + user.classId);
    }
}

点击提交之后,可以看到,在浏览器控制台中,就打印出来了服务器的响应数据~~
当前使用的是 ajax 的方式来提交数据,这个操作默认不会产生页面跳转,就和咱们使用 form 风格差别很大~~

在这里插入图片描述

抓包可以观察 body 数据的格式:

POST http://127.0.0.1:8080/hello102/postJson HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Content-Length: 32
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="100", "Google Chrome";v="100"
Accept: */*
Content-Type: application/json
X-Requested-With: XMLHttpRequest
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36
sec-ch-ua-platform: "Windows"
Origin: http://127.0.0.1:8080
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:8080/hello102/test.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

{"userId":"111","classId":"222"}

Postman 构造 JSON 请求

在这里插入图片描述


四、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 中写入二进制格式数据

addHeader 例如 Set-Cookie,一个响应中,是可以有多个 Set-Cookie 这样的 key 的

sendRedirect 构造一个 302 重定向响应

getWriter getOutputStream 往 body 中写数据,更多的是使用文本的方式

  • 注意: 响应对象是服务器要返回给浏览器的内容,这里的重要信息都是程序猿设置的,因此上面的方法都是 “写” 方法.
  • 注意: 对于状态码 / 响应头的设置要放到 getWriter / getOutputStream 之前,否则可能设置失效

2、代码示例:设置状态码

创建 StatusServlet 类

——200:

@WebServlet("/status")
public class StatusServlet extends HttpServlet {
    
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        // super.doGet(req, resp);
        resp.setStatus(200);
        resp.getWriter().write("hello");
    }
}

访问:http://127.0.0.1:8080/hello102/status

显示:hello

抓包结果:

HTTP/1.1 200
Content-Length: 5
Date: Sat, 21 May 2022 13:30:30 GMT
Keep-Alive: timeout=20
Connection: keep-alive

hello

——404:

resp.setStatus(200);

重新部署,访问

显示:hello

开发者工具显示:GET http://127.0.0.1:8080/hello102/status 404

抓包结果:

HTTP/1.1 404
Content-Length: 5
Date: Sat, 21 May 2022 13:33:09 GMT
Keep-Alive: timeout=20
Connection: keep-alive

hello

注意: 服务器返回的状态码,只是在告诉浏览器,当前的响应是个啥状态,并不影响浏览器照常去显示 body 中的内容~~


3、代码示例:自动刷新

实现一个程序,让浏览器每秒钟自动刷新一次,并显示当前的时间戳

例如 “文字直播”:

这是在十年前左右的时候 4G 和 5G 还没有出来,流量还是很贵的时候,使用手机看视频 / 看直播,都是不太现实的,当时一种流行的方式就是文字直播

各种比赛,当时就是有一些专门文字直播平台,有工作人员,比赛现场的情况,用文字的方式来描述出来
经常要涉及到页面刷新~~

创建 AutoRefreshServlet 类

@WebServlet("/autoRefresh")
public class AutoRefreshServlet extends HttpServlet {
    
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        resp.setHeader("Refresh", "1"); // 每隔一秒刷新一次
        resp.getWriter().write("timeStamp: " + System.currentTimeMillis());
    }
}

访问:http://127.0.0.1:8080/hello102/autoRefresh

显示结果:timeStamp: 1653140698925
这段数字是每隔一秒左右在不断变化的

抓包结果:

HTTP/1.1 200
Refresh: 1
Content-Length: 24
Date: Sat, 21 May 2022 13:48:02 GMT
Keep-Alive: timeout=20
Connection: keep-alive

timeStamp: 1653140882691

4、代码示例:重定向

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

创建 RedirectServlet 类

@WebServlet("/redirect")
public class RedirectServlet extends HttpServlet {
    
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    
    
        // 返回一个 302 重定向,让浏览器自动跳转到 sogou 主页
        resp.setStatus(302);
        resp.setHeader("Location", "https://www.sogou.com");
    }
}

抓包结果:

在这里插入图片描述

HTTP/1.1 302
Location: http://www.sogou.com
Content-Length: 0
Date: Mon, 21 Jun 2021 08:17:26 GMT
Keep-Alive: timeout=20
Connection: keep-alive

Servlet 提供了一个更简便的实现重定向的写法:

resp.sendRedirect("https://www.sogou.com");
  • 光理解了 Servlet API 还不足以支撑我们写出一个功能完整的网站
  • 还需要理解一个网站的开发过程大概是怎样的,理解这里的一些基本的编程思维和设计思路~~ (通过更多的案例来进行强化的)

猜你喜欢

转载自blog.csdn.net/qq_56884023/article/details/125684506