Servlet API 详解

导读:本篇文章讲解 Servlet API 详解,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

一、HttpServlet 类

我们写 Servlet 代码的时候, 首先第一步就是先创建类, 继承自 HttpServlet, 并重写其中的某些方法。

1.1 核心方法

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

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

destroy 不一定真的能调到!分两种情况:

  1. 杀进程,此时 destroy 无法被调用。比如点击 idea 红色方框,或者 cmd 直接点 ×,或者通过任务管理器结束任务…
  2. 8005 端口,是用来控制 tomcat 的。通过这个端口给 tomcat 发送一个关闭操作,这个时候 tomcat 就可以正常关闭,就能调用到 destroy ~

service:
tomcat 收到请求,实际上是先调用 service,在 service 里面再去根据方法,调用不同的 doXXX。
实际开发,很少会重写 service,就是重写 doXXX 就够了 ~~

这些方法的调用时机, 就称为 “Servlet 生命周期” (也就是描述了一个 Servlet 实例从生到死的过程)
在这里插入图片描述
注意: HttpServlet 的实例只是在程序启动时创建一次, 而不是每次收到 HTTP 请求都重新创建实例。
面试题: 请描述 Servlet 的生命周期是什么?
Servlet生命周期描述的是Servlet创建到销毁的过程:

  1. 当一个请求从HTTP服务器转发给Servlet容器时,容器检查对应的Servlet是否创建,没有创建就实例化该Servlet,并调用 init() 方法,init() 方法只调用一次,之后的请求都从第二步开始执行;
  2. 请求进入 service() 方法,根据请求类型转发给对应的方法处理,如doGet, doPost, 等等
  3. 容器停止前,调用 destory() 方法,进行清理操作,该方法只调用一次,随后JVM回收资源。

1.2 代码示例

在 java 目录中创建一个类 HelloServlet,代码如下:
在这里插入图片描述

解释:

  • 创建一个类 HelloServlet,继承自 HttpServlet
  • 在这个类上方加上 @WebServlet(“/hello”) 注解,表示 Tomcat 收到的请求中,路径为 /hello 的请求才会调用 HelloServlet 这个类的代码 (这个路径未包含 Context Path)。
  • 重写 doGet 方法。 doGet 的参数有两个,分别表示收到的 HTTP 请求 和要构造的 HTTP 响应。 这个方法会在 Tomcat 收到 GET 请求时触发。
    一个服务器的工作流程就可以分成三个典型的步骤:1)接收请求并解析;2)根据请求计算响应;3)构造响应数据,并返回给客户端
    1和3这两步,tomcat 已经帮我们做好了;2这步就是咱们程序猿自己要实现的逻辑,也就是doGet等方法要实现的内容!
  • HttpServletRequest 表示 HTTP 请求。Tomcat 按照 HTTP 请求的格式把字符串格式的请求转成了一个 HttpServletRequest 对象,后续想获取请求中的信息 (方法、url、header、body 等) 都是通过这个对象来获取。
  • HttpServletResponse 表示 HTTP 响应。代码中把响应对象构造好 (构造响应的状态码、header、body 等)
  • resp.getWriter() 会获取到一个流对象,通过这个流对象就可以写入一些数据,写入的数据会被构造成一个 HTTP 响应的 body 部分 (可通过 Fiddler 抓包查看),Tomcat 会把整个响应转成字符串,通过 socket 写回给浏览器。
  • System.out.println() 依然是在控制台打印。

上面的代码虽然只有寥寥几行,但是包含的信息量是巨大的!

  1. 我们的代码不是通过 main 方法作为入口了。main 方法已经被包含在 Tomcat 里,我们写的代码会被 Tomcat 在合适的时机调用起来。此时我们写的代码并不是一个完整的程序,而是 Tomcat 这个程序的一小部分逻辑。
  2. 我们随便写个类都能被 Tomcat 调用吗?满足啥样条件才能被调用呢?主要满足三个条件:
    a) 创建的类需要继承自 HttpServlet;
    b) 这个类需要使用 @WebServlet 注解关联上一个 HTTP 的路径;
    c) 这个类需要实现 doXXX 方法。
    当这三个条件都满足之后,Tomcat 就可以找到这个类,并且在合适的时机进行调用。

二、HttpServletRequest 类

当 Tomcat 通过 Socket API 读取 HTTP 请求 (字符串), 并且按照 HTTP 协议的格式把字符串解析成 HttpServletRequest 对象。

2.1 核心方法

方法 描述
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。
InputStream getInputStream() 用于读取请求的 body 内容,返回一个 InputStream 对象。

通过这些方法可以获取到一个请求中的各个方面的信息。

注意: 请求对象是服务器收到的内容, 不应该修改。因此上面的方法也都只是 “读” 方法, 而不是 “写” 方法 ~~

2.2 代码示例: 打印请求信息

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 {
        // resp 是响应对象, setContentType, 给响应的 ContentType 设置了值. html
        // 声明响应 body 是 html 结构的数据.
        resp.setContentType("text/html");

        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(req.getProtocol());
        stringBuilder.append("<br>");
        stringBuilder.append(req.getMethod());
        stringBuilder.append("<br>");
        stringBuilder.append(req.getRequestURI());
        stringBuilder.append("<br>");
        stringBuilder.append(req.getContextPath());
        stringBuilder.append("<br>");
        stringBuilder.append(req.getQueryString());
        stringBuilder.append("<br>");

        // 把请求的 header 也拼进来
        Enumeration<String> headerNames = req.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String name = headerNames.nextElement();
            String value = req.getHeader(name);
            stringBuilder.append(name + ": " + value);
            stringBuilder.append("<br>");
        }

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

部署程序。在浏览器通过 URL http://127.0.0.1:8080/yyhjava/showRequest 访问, 可以看到

在这里插入图片描述

如果不设置 content type,此时浏览器就懵逼了,会尝试猜一猜这里的 body 是啥格式 (猜的结果不一定对) !!!所以要一定要记得设置数据格式 ~~
格式有很多种:text/plain;text/html;text/css;application/js;application/json;image/png…

2.3 代码示例: 获取 GET 请求中的参数

2.3.1 获取 query string 中的参数

GET 请求中的参数一般都是通过 query string 传递给服务器的,形如 https://v.bitedu.vip/personInf/student?userId=1111&classId=100。此时浏览器通过 query string 给服务器传递了两个参数:userId 和 classId,值分别是 1111 和 100 。在服务器端就可以通过 getParameter 来获取到参数的值。

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 {
        // 获取 query string 中的键值对.
        // 假设浏览器的请求形如 ?studentId=10&studentName=张三
        String studentId = req.getParameter("studentId");
        String studentName = req.getParameter("studentName");
        System.out.println(studentId);
        System.out.println(studentName);

		// 需要告知格式和编码方式!
        resp.setContentType("text/html; charset=utf8");
        resp.getWriter().write(studentId + ", " + studentName);
    }
}

如果通过 http://127.0.0.1:8080/yyhjava/getParameter?studentId=10&studentName=张三 访问,可以看到:
在这里插入图片描述
此时说明服务器已经获取到客户端传递过来的参数 ~~

getParameter 的返回值类型为 String,必要的时候需要手动把 String 转成 int。

这里我们没有 URL encode,为什么也正常运行了呢?
不进行 URL encode,不是 100% 就会出错的 (有的浏览器,有的版本没事儿)。
但是还是要记得 encode 一下,encode助手:http://www.urlencode.com.cn/
所以,通过 http://127.0.0.1:8080/yyhjava/getParameter?studentId=10&studentName=%E5%BC%A0%E4%B8%89 访问。
(servlet getParameter 会自动针对 URL encode 的结果进行 decode,不需要咱们手动处理)

getParameter 获取键值对的时候:
如果键不存在,得到的是 null;
如果键存在但值不存在,得到的是 ” ” (空字符串)

2.3.2 获取 body 中的参数

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

1)x-www-form-urlencoded 数据格式

通过 form 表单 等方式构造 POST 请求,body 一般是 x-www-form-urlencoded 格式 ~~

这种情况与 doGet 类似,仍然可以通过 getParameter 获取参数的值:
(完全可以写在一个类中)

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 {
        // 获取 query string 中的键值对.
        // 假设浏览器的请求形如 ?studentId=10&studentName=张三
        String studentId = req.getParameter("studentId");
        String studentName = req.getParameter("studentName");
        System.out.println(studentId);
        System.out.println(studentName);

 		// 需要告知格式和编码方式!
        resp.setContentType("text/html; charset=utf8");
        resp.getWriter().write(studentId + ", " + studentName);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 通过 body 获取, 发个 post 请求
        // 预期请求的 body 里是 studentId=10&studentName=张三
        req.setCharacterEncoding("utf8");
        String studentId = req.getParameter("studentId");
        String studentName = req.getParameter("studentName");
        System.out.println(studentId);
        System.out.println(studentName);

        // 响应这里设置字符集有两种写法. 但是还是建议使用 setContentType 完整写法
        // 设置的字符集只是一小部分, 还需要设置格式.
        resp.setContentType("text/html; charset=utf8");
        // resp.setCharacterEncoding("utf8");
        
		// 注意与上行代码的先后顺序不能颠倒!!!
		resp.getWriter().write(studentId + ", " + studentName);
    }
}
  • req.setCharacterEncoding("utf8");
    这是设置 解析请求 使用的字符集!告诉 servlet (tomcat) 如何解析 ~~
  • resp.setContentType("text/html; charset=utf8");
    这里的 utf8 是告诉浏览器如何解析响应 ~~
    响应这里设置字符集有两种写法 (还有 resp.setCharacterEncoding("utf8"); ),但是还是建议使用 setContentType 完整写法!

在这里插入图片描述

那么怎么构造 POST 请求呢?这里展示两种方法:

(博客链接:https://blog.csdn.net/yyhgo_/article/details/128454930?spm=1001.2014.3001.5501)

1)from 表单:

创建一个 student.html 文件,放到 webapp 目录中,形如
在这里插入图片描述
一个 Servlet 程序中可以同时部署静态文件,静态文件就放到 webapp 目录中即可 ~~

student.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>Document</title>
</head>
<body>
    <!-- 这是相对路径的写法, 此处基准目录就是 html 所在的目录, 也就是 yyhjava -->
    <form action="getParameter" method="post">
        <input type="text" name="studentId">
        <input type="text" name="studentName">
        <input type="submit" value="提交">
    </form>
    <!-- 这是绝对路径的写法 -->
    <!-- <form action="/yyhjava/getParameter"></form> -->
</body>
</html>

重新启动 tomcat 服务器,然后在浏览器中访问页面 http://127.0.0.1:8080/yyhjava/student.html
在这里插入图片描述

输入 10 张三,提交:
在这里插入图片描述
页面就出来了 ~~

过程中使用 Fiddler 抓包:
请求:
在这里插入图片描述
响应:
在这里插入图片描述


2)Postman:
在这里插入图片描述

Postman 也是 http 客户端,和浏览器是对等的 ~~

过程中使用 Fiddler 抓包:
请求:
在这里插入图片描述
响应:
在这里插入图片描述

2)json 数据格式

类似

{
	studentld: 20,
	studentName: "张三"
}

需要先读取 body 中的内容 (通过 getInputStream 读流对象);
然后进行解析。
问题来了,怎样进行解析呢?

比较麻烦,所以推荐使用一些第三方库:fastjson;Jackson;gson…
这里我们使用 Jackson (spring 御用的 json 库)!

在中央仓库 (链接:https://mvnrepository.com/) 搜索 Jackson
在这里插入图片描述
点进去,随便挑一个版本,复制代码:
在这里插入图片描述
粘贴到 pom.xml 中:
在这里插入图片描述
标红的话,等一会儿下载 ~~

Jackson 只需要学会其中的 一个类、两个方法 即可 ~~

代码:

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 {
    // 1. 这个类里的属性务必是 public 或者带有 public 的 getter / setter
    //    否则 jackson 无法访问这个对象的属性
    // 2. 这个类务必要有无参版本的构造方法. 如果不写任何构造方法, 编译器能自动生成无参构造方法
    public int studentId;
    public String studentName;
}

@WebServlet("/json")
public class JsonServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 此处假设请求的 body 格式为
        // { studentId: 10, studentName: "张三" }
        // jackson 提供的核心的类
        // 一个方法叫做 readValue, 把 json 格式的数据转成 java 的对象
        // 还有一个方法叫做 writeValueAsString, 把 java 对象转成 json 格式的字符串
        ObjectMapper objectMapper = new ObjectMapper();
        // readValue 第一个参数可以是字符串, 也可以是输入流.
        // 第二个参数, 是一个类对象. 也就是要解析出来的结果的对象的类.
        Student s = objectMapper.readValue(req.getInputStream(), Student.class);
        System.out.println(s.studentId);
        System.out.println(s.studentName);

        // resp.setContentType("application/json; charset=utf8");
        // resp.getWriter().write(objectMapper.writeValueAsString(s));
        resp.setContentType("text/html; charset=utf8");
        resp.getWriter().write(s.studentId + ", " + s.studentName);
        // objectMapper.writeValue(resp.getWriter(), s);
    }
}

注意代码中的注释!!!

在这里插入图片描述
在这里插入图片描述

两个方法:

  • ObjectMapper 的 readValue 方法能直接从一个 InputStream 对象读取数据
  • ObjectMapper 的 writeValueAsString 方法能把一个对象数组直接转成 JSON 格式的字符串

重新启动 tomcat 服务器,然后使用 Postman 构造一个请求:
在这里插入图片描述

在 Postman 中写 json 格式的时候,务必要保证这里的 key 是带引号的!!!
正常的 json,key 都得带引号,而 js 里的对象是不必带的 ~~

服务器这边也顺利地打印出了相关的结果:
在这里插入图片描述


类似的复杂 json 也是同理:
在这里插入图片描述

三、HttpServletResponse 类

Servlet 中的 doXXX 方法的目的就是根据请求计算得到相应, 然后把响应的数据设置到 HttpServletResponse 对象中。
然后 Tomcat 就会把这个 HttpServletResponse 对象按照 HTTP 协议的格式, 转成一个字符串, 并通过 Socket 写回给浏览器。

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

注意: 响应对象是服务器要返回给浏览器的内容,这里的重要信息都是程序猿设置的,因此上面的方法都是 “写” 方法 ~~

注意: 对于状态码/响应头的设置要放到 getWriter / getOutputStream 之前,否则可能设置失效!

3.2 代码示例: 设置状态码

实现一个程序,用户在浏览器通过参数指定要返回响应的状态码:

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 {
        // 约定, 浏览器 query string 传个参数过来.
        // 形如 type=1
        // 如果 type 为 1, 返回 200; type 为 2, 返回404; type 为 3 返回一个 500;
        String type = req.getParameter("type");
        if (type.equals("1")) {
            resp.setStatus(200);
        } else if (type.equals("2")) {
            resp.setStatus(404);
            // sendError 效果就是返回一个 tomcat 自带的错误页面.
            resp.sendError(404);
        } else if (type.equals("3")) {
            resp.setStatus(500);
        } else {
            resp.setStatus(504);
        }
    }
}

通过类似于 http://127.0.0.1:8080/yyhjava/status?type=2 的访问,就可以得到不同的响应页面。

3.3 代码示例: 自动刷新

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

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.setHeader("refresh", "2");
        resp.getWriter().write(System.currentTimeMillis() + "");
    }
}

通过 http://127.0.0.1:8080/yyhjava/autoRefresh 访问:
在这里插入图片描述
在这里插入图片描述
时间戳不断变化 ~~

当前设置的是每 2s 刷新—次,但实际并不是精确的 2000ms,会比 2000 略多一点!?
调度要消耗时间 / 网络传输消耗时间 / 服务器响应 … 再加上对于 ms 级别的计时存在误差 ~~

3.4 代码示例: 重定向

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

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.setStatus(302);
//        resp.setHeader("Location", "https://www.sogou.com");
        resp.sendRedirect("https://www.sogou.com");
    }
}

通过 http://127.0.0.1:8080/yyhjava/redirect 访问页面,会直接跳转到 https://www.sogou.com ~~

通过 Fiddler 抓包:
响应报文:
在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/118565.html

(0)
seven_的头像seven_bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!