【Servlet】Servlet API 详解

一. HttpServlet

1. 核心方法

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

我们实际开发的时候主要重写 doXXX 方法, 很少会重写 init / destory / service .
这些方法的调用时机, 就称为 “Servlet 生命周期”. (也就是描述了一个 Servlet 实例从生到死的过程).

在这里插入图片描述
注意: HttpServlet 的实例只是在程序启动时创建一次. 而不是每次收到 HTTP 请求都重新创建实例.

2. 代码示例: 处理 GET 请求

创建 MethodServlet.java, 创建 doGet 方法

@WebServlet("/method")
public class MethodServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        resp.getWriter().write("GET response");
   }
}

创建 testMethod.html, 放到 webapp 目录中, 形如
在这里插入图片描述
testMethod.html 内容:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <button onclick="sendGet()">发送 GET 请求</button>
    <script>
        function sendGet() {
            ajax({
                method: 'GET',
                url: 'method',
                callback: function (body, status) {
                    console.log(body);
                }
            });
        }
        // 把之前封装的 ajax 函数拷贝过来
        function ajax(args) {
            var xhr = new XMLHttpRequest();
            xhr.onreadystatechange = function () {
                // 0: 请求未初始化
                // 1: 服务器连接已建立
                // 2: 请求已接收
                // 3: 请求处理中
                // 4: 请求已完成,且响应已就绪
                if (xhr.readyState == 4) {
                    args.callback(xhr.responseText, xhr.status)
                }
            }
            xhr.open(args.method, args.url);
            if (args.contentType) {
                xhr.setRequestHeader('Content-type', args.contentType);
            }
            if (args.body) {
                xhr.send(args.body);
            } else {
                xhr.send();
            }
        }
    </script>
</body>
</html>

重新部署程序, 使用 URL http://localhost:8080/test/testMethod.html 访问页面

注意:

  • http://localhost:8080/test/testMethod.html 这个路径里面的 /test 是自己定义的 Context Path
  • 这个 ajax 请求的 URL 路径. 代码中写的 URL url: ‘method’, 为一个相对路径, 最终真实发
    送的请求的 URL 路径为 /test/method

在这里插入图片描述

点击 “发送 GET 请求” 按钮, 即可在控制台看到响应内容.
在这里插入图片描述

通过 Fiddler 抓包, 可以看到,

  • 当浏览器中输入 URL 之后, 浏览器先给服务器发送了一个 HTTP GET 请求
    在这里插入图片描述

  • 当点击 “发送 GET 请求” 按钮, 浏览器又通过 ajax 给服务器发送了一个 HTTP GET 请求
    在这里插入图片描述

在这里插入图片描述

3. 关于乱码问题

如果我们在响应代码中写入中文, 例如

resp.getWriter().write("GET 响应");

得到的结果:
在这里插入图片描述

关于 “乱码”:

  • 中文的编码方式有很多种. 其中最常见的就是 utf-8 .
  • 如果没有显式的指定编码方式, 则浏览器不能正确识别编码, 就会出现乱码的情况.
  • 可在代码中, 通过 resp.setContentType(“text/html; charset=utf-8”); 显式指定编码方式.
@WebServlet("/method")
public class MethodServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");
        resp.getWriter().write("GET 响应");
    }
}

更改后的响应
在这里插入图片描述

通过抓包可以看到, 当加上了 resp.setContentType(“text/html; charset=utf-8”); 代码之后, 响应中多了 Content-Type 字段, 内部指定了编码方式. 浏览器看到这个字段就能够正确解析中文了.
在这里插入图片描述

4. 代码示例: 处理 POST 请求

在 MethodServlet.java 中, 新增 doPost 方法.

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws
ServletException, IOException {
    resp.setContentType("text/html; charset=utf-8");
    resp.getWriter().write("POST 响应");
}

在 testMethod.html 中, 新增一个按钮, 和对应的点击事件处理函数

    <button onclick="sendPost()">发送 POST 请求</button>
    <script>
        function sendPost() {
            ajax({
                method: 'POST',
                url: 'method',
                callback: function (body, status) {
                    console.log(body);
                }
            })
        }
    </script>

重新部署程序, 使用 URL http://127.0.0.1:8080/test/testMethod.html 访问页面.

在这里插入图片描述

点击 “发送 POST 请求” 按钮, 可以在控制台中看到结果:

在这里插入图片描述

通过类似的方式还可以验证 doPut, doDelete 等方法.

二. HttpServletRequest

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

1. 核心方法

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

创建 ShowRequest 类

@WebServlet("/showRequest")
public class ShowRequest extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");
        StringBuilder respBody = new StringBuilder();
        respBody.append(req.getProtocol()); // 请求协议的名称和版本
        respBody.append("<br>");
        respBody.append(req.getMethod()); // 请求的 HTTP 方法
        respBody.append("<br>");
        respBody.append(req.getRequestURI()); // 从协议名称直到 HTTP 请求的第一行的查询字符串
        respBody.append("<br>");
        respBody.append(req.getContextPath()); // 上下文 (一级路径)
        respBody.append("<br>");
        respBody.append(req.getQueryString()); //  URL 中的查询字符串
        respBody.append("<br>");
        respBody.append("<h3>headers:</h3>");
        Enumeration<String> headerNames = req.getHeaderNames(); // 请求中包含的所有的头名
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            respBody.append(headerName + " ");
            respBody.append(req.getHeader(headerName));
            respBody.append("<br>");
        }
        resp.getWriter().write(respBody.toString());
    }
}

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

在这里插入图片描述

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

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

创建 GetParameter 类

@WebServlet("/getParameter")
public class GetParameter extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) 
throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");
        String userId = req.getParameter("userId");
        String classId = req.getParameter("classId");
        resp.getWriter().write("userId: " + userId + ", " + "classId: " +
classId);
   }
}

重新部署程序, 在浏览器中通过 http://127.0.0.1:8080/test/getParameter 访问, 可以看到:
当没有 query string的时候, getParameter 获取的值为 null.

在这里插入图片描述

如果通过 http://127.0.0.1:8080/test/getParameter?userId=123&classId=456 访问, 可以看到
在这里插入图片描述
此时说明服务器已经获取到客户端传递过来的参数.
getParameter 的返回值类型为 String. 必要的时候需要手动把 String 转成其他类型

4. 代码示例: 获取 POST 请求中的参数(1)

POST 请求的参数一般通过 body 传递给服务器. body 中的数据格式有很多种. 如果是采用 form 表单的形式, 仍然可以通过 getParameter 获取参数的值.

创建类 PostParameter

@WebServlet("/postParameter")
public class PostParameter extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");
        String userId = req.getParameter("userId");
        String classId = req.getParameter("classId");
        resp.getWriter().write("userId: " + userId + ", " + "classId: " +
                classId);
    }
}

创建 testPost.html, 放到 webapp 目录中

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <form action="postParameter" method="POST">
        <input type="text" name="userId">
        <input type="text" name="classId">
        <input type="submit" value="提交">
    </form>
</body>
</html>

重新部署程序, 通过 URL http://127.0.0.1:8080/test/testPost.html 访问, 可以看到 HTML

在这里插入图片描述
在输入框中输入内容, 点击提交
在这里插入图片描述
可以看到跳转到了新的页面, 并显示出了刚刚传入的数据.
在这里插入图片描述
此时通过抓包可以看到, form 表单构造的 body 数据的格式为:

在这里插入图片描述

5. 代码示例: 获取 POST 请求中的 JSON (2)

如果 POST 请求中的 body 是按照 JSON 的格式来传递, 那么获取参数的代码就要发生调整.

创建 PostParameterJson 类

@WebServlet("/postParameterJson")
public class PostParameterJson extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.setContentType("application/json;charset=utf-8");
        String body = readBody(req);
        resp.getWriter().write(body);
    }
    private String readBody(HttpServletRequest req) throws IOException {
        int contentLength = req.getContentLength();
        byte[] buffer = new byte[contentLength];
        InputStream inputStream = req.getInputStream();
        inputStream.read(buffer);
        return new String(buffer, "utf-8");
    }
}

创建 testPostJson.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <button onclick="sendJson()">发送 JSON 格式 POST 请求</button>
    <script>
        function sendJson() {
            ajax({
                url: 'postParameterJson',
                method: 'POST',
                contentType: 'application/json; charset=utf-8',
                body: JSON.stringify({ userId: 123, classId: 456 }),
                callback: function (body, status) {
                    console.log(body);
                }
            });
        }

        function ajax(args) {
            // 函数体略.... 参考之前封装的版本.
        }
    </script>
</body>
</html>

在浏览器中通过 http://127.0.0.1:8080/test/testPostJson.html 访问, 可以看到
在这里插入图片描述
点击按钮, 则浏览器就会给服务器发送一个 POST 请求, body 中带有 JSON 格式.
在这里插入图片描述
响应:
在这里插入图片描述

注意: 到目前为止, 服务器拿到的 JSON 数据仍然是一个整体的 String 类型, 如果要想获取到 userId 和 classId 的具体值, 还需要搭配 JSON 库进一步解析, 还不能根据 key 获取 value

6. 代码示例: 获取 POST 请求中的 JSON(3)

引入 Jackson 这个库, 进行 JSON 解析.

  1. 在中央仓库中搜索 Jackson, 选择 JackSon Databind
    在这里插入图片描述

  2. 把中央仓库中的依赖配置添加到 pom.xml 中, 形如:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.3</version>
</dependency>
  1. 在 PostParameterJson 类中修改代码
// 创建一个新的类表示 JSON 数据, 属性的名字需要和 JSON 字符串中的 key 一致. 
class JsonData {
    public String userId;
    public String classId;
}
@WebServlet("/postParameterJson")
public class PostParameterJson extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        String body = readBody(req);
        // 创建 ObjectMapper 对象. 这个是 Jackson 中的核心类. 
        ObjectMapper objectMapper = new ObjectMapper();
        // 通过 readValue 方法把 body 这个字符串转成 JsonData 对象
        JsonData jsonData = objectMapper.readValue(body, JsonData.class);
        resp.getWriter().write("userId: " + jsonData.userId + ", " + "classId: "
                + jsonData.classId);
    }
    private String readBody(HttpServletRequest req) throws IOException {
        int contentLength = req.getContentLength();
        byte[] buffer = new byte[contentLength];
        InputStream inputStream = req.getInputStream();
        inputStream.read(buffer);
        return new String(buffer, "utf-8");
    }
}

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

注意:

  • JsonData 这个类用来表示解析之后生成的 JSON 对象. 这个类的属性的名字和类型要和 JSON 字符串的 key 相对应.
  • Jackson 库的核心类为 ObjectMapper. 其中的 readValue 方法把一个 JSON 字符串转成 Java 对象. 其中的 writeValueAsString 方法把一个 Java 对象转成 JSON 格式字符串.
  • readValue 的第二个参数为 JsonData 的 类对象. 通过这个类对象, 在 readValue 的内部就可以借助反射机制来构造出 JsonData 对象, 并且根据 JSON 中 key 的名字, 把对应的 value 赋值给 JsonData 的对应相同的字段.

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

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

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

创建 StatusServlet 类

@WebServlet("/statusServlet")
public class StatusServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String statusString = req.getParameter("status");
        if (statusString != null) {
            resp.setStatus(Integer.parseInt(statusString));
       }
        resp.getWriter().write("status: " + statusString);
   }
}

部署程序, 在浏览器中通过 URL http://127.0.0.1:8080/test/statusServlet?status=200 访问, 可以看到

在这里插入图片描述
抓包结果:
在这里插入图片描述

变换不同的 status 的值, 就可以看到不同的响应结果.

3. 代码示例: 自动刷新

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

@WebServlet("/autoRefreshServlet")
public class AutoRefreshServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setHeader("Refresh", "1");
        long timeStamp = new Date().getTime();
        resp.getWriter().write("timeStamp: " + timeStamp);
   }
}
  • 通过 HTTP 响应报头中的 Refresh 字段, 可以控制浏览器自动刷新的时机.
  • 通过 Date 类的 getTime 方法可以获取到当前时刻的毫秒级时间戳.

部署程序, 通过 URL http://127.0.0.1:8080/test/autoRefreshServlet 访问, 可以看到浏览器每秒钟自动刷新一次.

在这里插入图片描述
抓包结果
在这里插入图片描述

4. 代码示例: 重定向

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

创建 RedirectServlet 类

@WebServlet("/redirectServlet")
public class RedirectServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.sendRedirect("http://www.sogou.com");
    }
}

部署程序, 通过 URL http://127.0.0.1:8080/test/redirectServlet 访问, 可以看到, 页面自动跳转到 搜狗主页 了.

抓包结果
在这里插入图片描述
在这里插入图片描述

好啦! 以上就是对 Servlet API 的讲解,希望能帮到你 !
评论区欢迎指正 !

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值