Servlet(下篇)

哥几个来学 Servlet 啦 ~~

Servlet(下篇)_第1张图片

这个是 Servlet(上篇)的链接,

(2条消息) Servlet (上篇)_小枫 ~的博客-CSDN博客icon-default.png?t=N5K3https://blog.csdn.net/m0_64247824/article/details/131229873主要讲了 Servlet的定义、Servlet的部署方式、Servlet 的运行原理、Servlet 的生命周期 ~~

本篇主要讲解 HttpServlet 类 ~~

目录

一、HttpServlet 类

核心方法:

① 代码示栗:处理 GET 请求 和 POST 请求

二、Response 乱码问题

三、HttpServletRequest

① 代码示栗:打印请求信息

② 代码示栗:通过 query string 获取 GET 请求中的参数

③ 代码示栗:采用 form 表单获取 POST 请求中的参数

Request 乱码问题

④ 代码示栗:JSON 格式 获取 POST 请求中的参数

四、HttpServletResponse

① 代码示栗:设置状态码

② 代码示栗:自动刷新

③ 代码示栗:重定向


一、HttpServlet 类

HttpServlet 是继承于 GenericServlet 抽象类而来的。

而这个  GenericServlet 抽象类又实现了 Servlet、ServletConfig、Serializable 接口

因此, HttpServlet类 中实现了 Servlet 里的方法(比如我们上一篇所说的 init() 、service()、destroy() 等方法)

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

核心方法:

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

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

因为 service()方法 会解析HttpServletRequest中的方法参数,并调用以下方法之一:doGet() 、doPost() 、doHead() 、doPut() 、doTrace() 、doOptions() 和doDelete() 。

service()方法 源码:

protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        long lastModified;
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader("If-Modified-Since");
                if (ifModifiedSince < lastModified) {
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
                    resp.setStatus(304);
                }
            }
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }

    }

使用 HttpServlet 抽象类时,还需要借助分别代表Servlet请求和Servlet响应的HttpServletRequest和HttpServletResponse对象。

我们以 doGet()方法 来举例:

Servlet(下篇)_第2张图片

补全完之后,要把代码中的 “super.doGet(req, resp);” 删除!!

Servlet(下篇)_第3张图片

可以看见里面要传两个参数:1. HttpServletRequest 对象  2. HttpServletResponse 对象。

Servlet(下篇)_第4张图片

 HttpServletRequest 为 服务器 从 客户端 收到的 请求

HttpServletResponse 为 服务器 要 发送给 客户端 而构造出来的 响应

① 代码示栗:处理 GET 请求 和 POST 请求

创建 ServletMethod.java, 创建 doGet 方法 和 doPost 方法

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 ServletMethod extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 这行代码相当于用于往 响应的 body 中写入文本格式数据
        resp.getWriter().write("Get response");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 这行代码相当于用于往 响应的 body 中写入文本格式数据
        resp.getWriter().write("Post response");
    }
}

同时,我们创建 testMethod.html, 放到 webapp 目录中,形如:

Servlet(下篇)_第5张图片

 注意:这个文件与 WEB-INF 同级

然后在 testMethod.html 里写入:




    
    
    TestMethod

    
    



    
    
    




重新部署程序,在浏览器中通过

TestMethodhttp://localhost:8080/HelloServlet/testMethod.html访问,可以看到:

Servlet(下篇)_第6张图片

用 Fiddler 抓包可以看到它们两个请求的响应:

GET 请求的响应:

Servlet(下篇)_第7张图片

POST 请求的响应 

Servlet(下篇)_第8张图片

二、Response 乱码问题

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

Servlet(下篇)_第9张图片

此时在浏览器访问的时候,会看到 "乱码" 的情况:

Servlet(下篇)_第10张图片

 关于 "乱码":

中文的编码方式有很多种,其中最常见的就是 utf-8 。

如果没有显式的指定编码方式,则浏览器不能正确识别编码,就会出现乱码的情况。

可以在代码中,通过 resp.setContentType("text/html; charset=utf-8"); 显式的指定编码方式:

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //这行代码相当于告诉 客户端的浏览器 : 我们返回的请求是一个 utf-8 格式的
        //resp 就是要返回给客户端的响应
        resp.setContentType("text/html;charset=utf-8");
        // 这行代码相当于用于往 响应的 body 中写入文本格式数据
        resp.getWriter().write("Get 响应");
    }

此时,就不会出现乱码现象了:

Servlet(下篇)_第11张图片

通过抓包也可以看出  Content-Type 被设置成了 text/html;charset=utf-8

Servlet(下篇)_第12张图片

        Request 乱码问题会在下面的“③ 代码示栗:采用 form 表单获取 POST 请求中的参数” 中提及。

三、HttpServletRequest

HttpServletRequest 可以理解为 服务器 从 客户端 收到的请求

一个 Http 请求中里包含什么内容,那么 这个 HttpServletRequest 就有什么参数。比如:方法、URL、版本号、header、body ...

核心方法:

方法 描述
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 对象。

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

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

① 代码示栗:打印请求信息

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 ShowRequest extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //这行代码相当于告诉 客户端的浏览器 : 我们返回的请求是一个 utf-8 格式的
        //resp 就是要返回给客户端的响应
        resp.setContentType("text/html;charset=utf-8");

        // 先创建一个 StringBuilder 类,让它来接收 客户端的浏览器 发过来的请求的信息
        StringBuilder respBody = new StringBuilder();

        //返回请求协议的名称和版本
        respBody.append(req.getProtocol());
        respBody.append("
");//由于返回的是 html 格式的代码,因此换行不能用 "\n",而是用 "
" //返回请求的 HTTP 方法的名称 respBody.append(req.getMethod()); respBody.append("
"); //从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请求的 URL 的一部分 respBody.append(req.getRequestURI()); respBody.append("
"); //返回指示请求上下文的请求 URI 部分 respBody.append(req.getContextPath()); respBody.append("
"); //返回包含在路径后的请求 URL 中的查询字符串 respBody.append(req.getQueryString()); respBody.append("
"); //获取header respBody.append("

headers:

"); //返回一个枚举,包含在该请求中包含的所有的头名 Enumeration headerNames = req.getHeaderNames(); while (headerNames.hasMoreElements()) { //一个一个获取,再一个一个拼装 String headerName = headerNames.nextElement(); respBody.append(headerName + " "); //通过 header名 获取header respBody.append(req.getHeader(headerName)); respBody.append("
"); } //这行代码相当于用于往 响应的 body 中写入文本格式数据 //注意:write()里的参数是String类型 resp.getWriter().write(respBody.toString()); } }

使用 Smart Tomcat 部署,访问:localhost:8080/HelloServlet/showRequest

Servlet(下篇)_第13张图片

② 代码示栗:通过 query string 获取 GET 请求中的参数

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

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

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

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

创建 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 GetParameter extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //这行代码相当于告诉 客户端的浏览器 : 我们返回的请求是一个 utf-8 格式的
        //resp 就是要返回给客户端的响应
        resp.setContentType("text/html;charset=utf-8");

        //获取 名为userId 的参数
        String userId = req.getParameter("userId");
        //获取 名为classId 的参数
        String classId = req.getParameter("classId");

        //这行代码相当于用于往 响应的 body 中写入文本格式数据
        resp.getWriter().write("userId = " + userId + "
classId = " + classId); } }

重新部署程序,在浏览器中通过 localhost:8080/HelloServlet/getParameter 访问,可以看到:

如果 url 后面不写参数,那么参数的值为 null

如果 url 后面写了参数,那么就可以获取到值了

③ 代码示栗:采用 form 表单获取 POST 请求中的参数

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

创建类 PostParameter

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 PostParameter extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/http;charset=utf-8");
        String userId = req.getParameter("userId");
        String classId = req.getParameter("classId");
        resp.getWriter().write("userId = " + userId + "   classId = " + classId);
    }
}

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




    
    
    TestPost


    

重新部署程序,通过 URL http://127.0.0.1:8080/ServletHelloWorld/testPost.html 访问

Servlet(下篇)_第14张图片

输入参数,点击提交之后

Servlet(下篇)_第15张图片

可以看到跳转到了新的页面,并显示出了刚刚传入的数据

 那如果输入的参数里含有中文呢?

Servlet(下篇)_第16张图片

 那么就会出现乱码

 

用 Fiddler 抓包 发现是一串 看不懂的字符

Servlet(下篇)_第17张图片

Request 乱码问题

解决post提交方式的乱码:

request.setCharacterEncoding("UTF-8");

Servlet(下篇)_第18张图片

结果:不再乱码

而 解决get提交的方式的乱码:

parameter = newString(parameter.getbytes("iso8859-1"),"utf-8");

④ 代码示栗:JSON 格式 获取 POST 请求中的参数

创建 PostParameterJson 类

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("/postParameterJson")
public class PostParameterJson extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //告诉 客户端浏览器 body 的格式是 json 格式的
        resp.setContentType("application/json;charset=utf-8");
        //处理请求里的body
        String body = readBody(req);
        //往 响应的 body 中写入文本格式数据
        resp.getWriter().write(body);
        //打印从请求中得到的Body
        System.out.println(body);
    }

    private String readBody(HttpServletRequest req) throws IOException {
        //获取请求正文的长度
        int contentLength = req.getContentLength();
        //创建一个byte数组来接收请求的body
        //为什么要用 byte 呢? 因为接收的时候是按照字节流接收的
        byte[] body = new byte[contentLength];
        //创建一个输入流对象
        InputStream inputStream = req.getInputStream();
        //将请求的body写入到数组中
        inputStream.read(body);
        //将 byte 数组转化为 String,并设置为 utf-8字符集
        return new String(body, "utf-8");
    }
}

创建 testPostJson.html




    
    
    testPostJson

    
    


    

        


在浏览器中通过 testPostJsonhttp://localhost:8080/HelloServlet/testPostJson.htmltestPostJson 访问, 可以看到

点击按钮,则浏览器就会给服务器发送一个 POST 请求,body 中带有 JSON 格式(使用 Fiddler 抓包):

Servlet(下篇)_第19张图片

服务器收到这个结果之后,又把数据返回了回去(打印到控制台上):

Servlet(下篇)_第20张图片

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

我们需要引入 Jackson 这个库, 进行 JSON 解析:

(1) 在中央仓库中搜索 Jackson,选择 JackSon Databind

Servlet(下篇)_第21张图片

(2) 选择 2.12.3 版本

(3) 把中央仓库中的依赖配置添加到 pom.xml 中, 形如

Servlet(下篇)_第22张图片

(4) 在 PostParameterJson 类中修改代码

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;
import java.io.InputStream;

//创建一个 类 来接收数据,这个类里的 成员变量 要与 Json 里的字符串的 key 相同~~
class JsonData {
    public String userId;
    public String password;
}

@WebServlet("/postParameterJson")
public class PostParameterJson extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //告诉 客户端浏览器 body 的格式是 html 格式的
        resp.setContentType("text/html;charset=utf-8");
        //处理请求里的body
        String body = readBody(req);

        //ObjectMapper类 是 Json 的核心类
        ObjectMapper objectMapper = new ObjectMapper();

        //用这个创建出来的 JsonData 类来接收数据
        // 通过 readValue() 方法把 body 这个字符串转成 JsonData 对象
        JsonData jsonData = objectMapper.readValue(body, JsonData.class);

        resp.getWriter().write("userId = " + jsonData.userId + "   password = " + jsonData.password);
    }

    private String readBody(HttpServletRequest req) throws IOException {
        //获取请求正文的长度
        int contentLength = req.getContentLength();
        //创建一个byte数组来接收请求的body
        //为什么要用 byte 呢? 因为接收的时候是按照字节流接收的
        byte[] body = new byte[contentLength];
        //创建一个输入流对象
        InputStream inputStream = req.getInputStream();
        //将请求的body写入到数组中
        inputStream.read(body);
        //将 byte 数组转化为 String,并设置为 utf-8字符集
        return new String(body, "utf-8");
    }
}

控制台结果:

Servlet(下篇)_第23张图片

注意:

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

四、HttpServletResponse

        HttpServletResponse 就是 服务器 构造出来的响应,并且要将这个响应返回给 客户端。

        Tomcat 就会把这个 HttpServletResponse 对象按照 HTTP 协议的格式,转成一个字符串,并通过 Socket 写回给浏览器。

核心方法:

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

注意:响应对象是服务器要返回给浏览器的内容,一个 HTTP 响应报文里有什么内容,程序员就可以设置什么内容。因此上面的方法都是 “写” 方法。

此外,对于 状态码 / 响应头 的设置要返回到 getWriter / getOutStream 之前,否则可能设置失效。

① 代码示栗:设置状态码

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

创建 StatusServlet 类:

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("/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));
        }

        //将响应里的状态码写到响应的 body 中
        resp.getWriter().write("status = " + resp.getStatus());
    }
}

访问 localhost:8080/HelloServlet/statusServlet

Servlet(下篇)_第24张图片

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

② 代码示栗:自动刷新

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

创建 AutoRefreshServlet 类:

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.Date;

@WebServlet("/autoRefreshServlet")
public class AutoRefreshServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //通过 设置 HTTP 的响应报文里的 Header 中的 Refresh 字段,可以实现自动刷新
        resp.setHeader("Refresh", "1");

        //获取毫米级的 时间戳
        long time = new Date().getTime();

        //将时间戳写入到相应的 Body 中
        resp.getWriter().write("TimeStamp = " + time);
    }
}

访问:localhost:8080/HelloServlet/statusServlet

抓包结果:

Servlet(下篇)_第25张图片

③ 代码示栗:重定向

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

创建 RedirectServlet 类:

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("/redirectServlet")
public class RedirectServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //设置状态码
        resp.setStatus(302);
        //使用指定的重定向位置 URL 发送临时重定向响应到客户端。
        resp.sendRedirect("https://www.csdn.net/");
    }
}

访问 http://localhost:8080/HelloServlet/redirectServlet 

发现成功跳转到了 CSDN 界面

Servlet(下篇)_第26张图片

 并且通过抓包可以看到 状态码被设置为 302 (临时重定向)

Servlet(下篇)_第27张图片

        虽然说 Servlet 是一门古老的技术,现在很多公司都用的是SpringMVC-Spring-MyBatis / SpringBoot 做开发了,但是它们都相当于 Servlet 的简化版,因此,学习完 Servlet ,你将有更好的基础去面对后面的框架的知识。 所以,加油吧 ~~

Servlet(下篇)_第28张图片

你可能感兴趣的:(网络通信,servlet,html,java-ee,http,网络)