【Servlet API详解】

🎉🎉🎉点进来你就是我的人了
博主主页:🙈🙈🙈戳一戳,欢迎大佬指点!

欢迎志同道合的朋友一起加油喔🤺🤺🤺


目录

1. HttpServlet类

Servlet的生命周期

处理GET请求

2. HttpServletRequest 

 前后端交互的几种方式

2.1 获取query String 

2.2 获取body (数据格式与query String一致)

2.3 获取body (json数据格式) 

(1) writeValue() 方法

(2) readValue()方法

3. HttpServletResponse

3.1 设置状态码  

3.2 ⾃动刷新

3.3 重定向



Servlet API中包含了很多的内容,但我们主要用到的是以下三个类,HttpServlet,HttpServletRequest,HttpServletResponse

1. HttpServlet类

在写Servlet代码的时候,第一步是创建一个类,继承HttpServlet,并重写其中的方法

方法调用时机
init在HttpServlet实例化之后被调用一次
destroy在HttpServelet实例不再使用时调用一次
service收到HTTP请求时调用 (由service调用)
doGet收到GET请求时调用 (由service调用)
doPost收到POST请求时调用 (由service调用)
doPut / doDelete…收到对应请求时调用 (由service调用)

init方法: 该方法是在tomcat首次收到了该类相关联(访问/hello路径的请求)的请求时,就会调用到HelloServlet,就需要先对HelloServlet进行实例化,后续在收到请求时,不必再实例化了,直接复用之前的HelloServlet实例即可,只执行一次

destroy方法: 当HttpServlet实例不再使用时调用该方法,啥时候该实例就不再使用了?服务器只要不停止,该实例就一直被使用,只有当服务器停止后了,才会调用该方法,只执行一次
这里的destroy能否被执行到,是存在争议的:
如果是通过停止按钮,这个本质操作是通过tomcat的8005端口,主动停止,才能触发destroy
如果是直接杀死进程,此时就来不及执行destroy
所以不建议在destroy内执行有效代码

service: 收到HTTP请求就会调用 (进到父类 HttpServlet 里面查看)

Service中根据请求的类型不同,调用不同的方法,doGet,doPost方法等等,会执行多次,每收到一次HTTP请求就执行一次

Servlet的生命周期

  1. 开始的时候执行init
  2. 每次收到请求后,执行service
  3. 销毁之前执行destroy

处理GET请求

  1. 直接在浏览器中,通过URL就能构造(GET请求最常用用法)
  2. 通过postman构造GET请求 (最简单)
  3. 通过ajax构造GRT请求
  • 下面演示ajax构造Get请求,首先创建MethodServlet.java类, 创建 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("/method")
public class HelloServlet2 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf8");
        resp.getWriter().write("这是一个 doGet");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf8");
        resp.getWriter().write("这是一个doPost");
    }

    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf8");
        resp.getWriter().write("这是一个doPut");
    }

    @Override
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf8");
        resp.getWriter().write("这是一个doDelete");
    }
}

创建  TestMethod.html, 放到 webapp 目录中,与WEB-INF处于同级关系

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
        $.ajax({
            type: 'get',
            url: 'method',
            success: function (body, status) {
                console.log(body);
            }
        });
    </script>
</body>
</html>

启动smart Tomcat一键打包部署后结果如下:

 注意路径匹配:

我们还需要注意是否加 / 的问题

我们想要处理其他方法也是同理,在html中将type类型修改,HelloServlet2方法中重写对应方法即可,我们每次修改完代码之后都需要重启服务器

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
Enumeration getHeaderNames()返回一个枚举,包含在该请求中包含的所有的头名
String getHeader(String name)以字符串形式返回指定的请求头的值
String getCharacterEncoding()返回请求主体中使用的字符编码
 void  setCharacterEncoding设置请求主体中使用的字符编码,防止乱码 (例如utf8)
String getContentType()返回请求主体的 MIME 类型,如果不知道类型则返回 null
int getContentLength()返回请求body的长度
InputStream getInputStream()用于读取请求的 body 内容. 返回一个 InputStream 对象
  • query string 是键值对结构,我们可以通过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("/showRequest")
public class ShowRequestServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        // 此处返回一个 HTML, 在 HTML 里面显示刚才看到的这些 API 的接口
        // 把这些 API 的返回结果往这个 StringBuilder 里面来拼
        StringBuilder html = new StringBuilder();
        html.append(req.getProtocol());     // HTTP版本号
        html.append("<br>");
        html.append(req.getMethod());       // 方法
        html.append("<br>");
        html.append(req.getRequestURI());   // 请求路径
        html.append("<br>");
        html.append(req.getContextPath());  // 上下文路径
        html.append("<br>");
        html.append(req.getQueryString());  // QueryString
        html.append("<br>");
        html.append("<br>");
        html.append("<br>");
        html.append("<br>");
        // 获取到请求的 header 头
        Enumeration<String> headers = req.getHeaderNames();
        // 请求中的 header 是一组键值对结构,循环相当于拿到键值对中所有的 key
        while (headers.hasMoreElements()) {
            String headerName = headers.nextElement();
            html.append(headerName);
            html.append(":");
            html.append((req.getHeader(headerName)));   // 根据 key 来获取 value
            html.append("<br>");
        }
 
        resp.getWriter().write(html.toString());
    }
}

浏览器响应结果如下:

 前后端交互的几种方式

  • GET请求传参通过query string
  • POST请求传参通过form表单
  • POST请求传参通过json

我们下来演示一下上述三种方法,前端给后端传参,我们后端获取请求中传来的参数

2.1 获取query String 

  • 前端直接通过地址栏构造一个URL发送给后端
  • 请求参数(useId = 10 & classId = 001)放在url的query string中
  • 后端重新doGET()使用getParameter()方法获取key (used / classld)对应的value (10 / 001)
  • 然后将数据返回给浏览器
@WebServlet("/getParameter")
public class GetParameterServelet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //使用getParameter获取前端query string的数据 useId = 10 & classId = 001
        String userId = req.getParameter("useId");
        String classId = req.getParameter("classId");
        resp.setContentType("text/html; charset=utf8");
        resp.getWriter().write(userId +", " + classId);
    }
}

 浏览器响应结果如下:

这里请求中的query string键值对会被tomcat处理成形似map结构的数据,我们就可以通过key去获取value了,需要注意的是我们这里的value都是String类型的,如果我们getParameter的参数前端并没有传递,那么我们的value就是null

2.2 获取body (数据格式与query String一致)

  • 通过form表单构造post请求
  • 请求的参数(useId = 10 & classId = 001)放在body 中
  • 后端重写doPost()使用getParameter()方法获取key (used / classld)对应的value (10 / 001)
  • 然后将数据返回给浏览器

 通过表单构造 get/post请求 详解可参考我上一篇博客(【HTTP协议】)

  text.html里面使用form表单构造请求

<!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>使用form表单构建一个请求</title>
</head>
<body>
    <form action="postParameter" method="post">
        <input type="text" name="useId">
        <input type="text" name="classId">
        <input type="submit" value="提交">
    </form>
</body>
</html>

 后端接收请求并返回结果

@WebServlet("/postParameter")
public class PostParameterServelet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //使用getParameter获取前端body的数据 useId = 10 & classId = 001
        String userId = req.getParameter("useId");
        String classId = req.getParameter("classId");
        resp.setContentType("text/html; charset=utf8");
        resp.getWriter().write(userId +", " + classId);
    }
}

 浏览器响应结果如下:

2.3 获取body (json数据格式) 

  1. postman构造出一个指定的post请求,body就是josn数据
  2. 请求到达tomcat,tomcat解析成req对象
  3. 在servlet代码中,req.getInputStream()读取body的内容
  4. 又把body内容构造成一个响应结果返回给浏览器(postman)
@WebServlet("/JsonParameter")
public class JsonParameterServelet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //通过这个方法处理body为json格式的数据
        //直接把req对象里的body完整的读出来
        // 1.先拿到body的长度,单位是字节
        int length = req.getContentLength();
        // 2.准备一个字节数组,来存放body的内容
        byte[] buffer = new byte[length];
        // 3.获取到InputStream对象
        InputStream inputStream = req.getInputStream();
        // 4. 读取数据,从InputStream对象中,读到数据,放到buffer这个数组中
        inputStream.read(buffer);
        //把这个数组构造成String,打印出来
        String body =  new String(buffer,0,length,"utf8");
        System.out.println("body = "+body);
        resp.getWriter().write(body);
    }
}

 通过postman构造请求(body为josn格式),服务器返回body给postman

当前通过 json 传递数据, 服务器这边只是把整个 body 读出来, 没有按照键值对的方式来处理.(还不能根据 key 获取 value),如果响应通过key获取value此时需要借助第三方库来处理。

引入依赖Jackson这个库在在中央仓库中获取(Jackson地址)。

        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.13.1</version>
        </dependency>

  将这段代码复制到pom.xml里面就可了。

  JsonParameterServlet.java 文件 

class Student {
    public String userId;
    public String classId;

    @Override
    public String toString() {
        return "Student{" +
                "userId='" + userId + '\'' +
                ", classId='" + classId + '\'' +
                '}';
    }
}

@WebServlet("/JsonParameter")
public class JsonParameterServelet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //通过这个方法处理body为json格式的数据
        //直接把req对象里的body完整的读出来
        // 1.先拿到body的长度,单位是字节
//        int length = req.getContentLength();
//        // 2.准备一个字节数组,来存放body的内容
//        byte[] buffer = new byte[length];
//        // 3.获取到InputStream对象
//        InputStream inputStream = req.getInputStream();
//        // 4. 读取数据,从InputStream对象中,读到数据,放到buffer这个数组中
//        inputStream.read(buffer);
//        //把这个数组构造成String,打印出来
//        String body =  new String(buffer,0,length,"utf8");
//        System.out.println("body = "+body);

        //使用json涉及到的核心对象
        ObjectMapper objectMapper = new ObjectMapper();
        //readValue 就是把一个 json 格式的字符串 转成java对象
        Student student =  objectMapper.readValue(req.getInputStream(),Student.class);
        resp.getWriter().write(student.toString());
        System.out.println(student.userId+", "+student.classId);;
    }
}

注意:Jackson库的核心类为ObjectMapper,该对象有两个方法

  • readValue: 把 json字符串转成 java对象
  • writeValue: 把 java 对象转成 json 格式字符串
  • 下面这行代码将请求的主体从JSON格式转换成一个Student类型的Java对象。

  1. 首先,使用req.getInputStream()从请求的输入流中读取JSON格式的数据。这个输入流通常包含请求的主体。

  2. 然后,ObjectMapperreadValue()方法使用Student.class作为参数来创建一个新的Student实例。在这个新对象创建时,它的所有属性都被初始化为它们的默认值。

  3. 接着,ObjectMapper把从输入流中读取的JSON字符串解析成一个内部的键值对数据结构。这个数据结构是一个Map<String, Object>类型,其中String代表JSON对象的字段名,Object代表相应的值。

  4. 然后,ObjectMapper开始遍历这个键值对数据结构。对于每一个键值对,ObjectMapper查找Student对象中名字相同的属性。如果找到,它就尝试将这个键值对的值转换成属性的类型(如果需要),然后把这个值赋给那个属性。如果在Student类中找不到匹配的属性,或者键值对的值不能被转换成属性的类型,那么ObjectMapper的行为将取决于其配置。在默认情况下,如果不能找到匹配的属性,ObjectMapper将忽略这个键值对;如果键值对的值不能被转换,那么ObjectMapper将抛出一个异常。

  5. 最后,readValue()方法返回一个Student对象。这个对象已经被从JSON数据解析出的值填充。如果在解析或填充过程中出现任何问题(例如JSON格式错误,或者键值对的值不能被转换成属性的类型),那么readValue()方法将抛出一个异常,而不是返回一个部分初始化的对象。

在这个过程中,ObjectMapperreadValue()方法将JSON数据的结构映射到Java对象的结构,这就是所谓的反序列化过程。


(1) writeValue() 方法

writeValue() 是 Jackson 库中的 ObjectMapper 类的一个方法,用于将 Java 对象转换为 JSON 格式的字符串,并将其输出到各种类型的目标(如 FileOutputStreamWriter)。

这个方法通常有两个参数:

1.第一个参数,通常是一个 FileOutputStreamWriter 对象,表示你希望将 JSON 数据写入到的目标。

  • 如果它是一个 File 对象,那么 JSON 数据将被写入到这个文件。例如,你可以创建一个新的文件并将其传递给 writeValue() 方法:
File file = new File("/path/to/your/file.json");
objectMapper.writeValue(file, yourObject);
  • 如果它是一个 OutputStream 对象,那么 JSON 数据将被写入到这个输出流。例如,你可以创建一个 FileOutputStream 并将其传递给 writeValue() 方法:
OutputStream outputStream = new FileOutputStream("/path/to/your/file.json");
objectMapper.writeValue(outputStream, yourObject);
  • 如果它是一个 Writer 对象,那么 JSON 数据将被写入到这个 writer。例如,你可以创建一个 FileWriter 并将其传递给 writeValue() 方法:
Writer writer = new FileWriter("/path/to/your/file.json");
objectMapper.writeValue(writer, yourObject);

2. 第二个参数是你想要转换为 JSON 格式的 Java 对象。这个对象可以是任何 Java 对象(如自定义的类的实例,或者是 ListMap 等集合类的实例),只要它可以被成功地转换为 JSON 格式。这个参数将被转换为 JSON,然后写入到第一个参数指定的目标。例如:

// 假设 Person 是你的一个自定义类
Person person = new Person();
person.setName("Alice");
person.setAge(25);
// 将 person 对象转换为 JSON 并写入到 file
objectMapper.writeValue(file, person);

(2) readValue()方法

readValue() 是 Jackson 库中的 ObjectMapper 类的一个方法,用于从各种类型的源(如 Stringbyte[]FileInputStreamReader)中读取 JSON 数据,并将其反序列化为 Java 对象。

这个方法通常有两个参数:

1.第一个参数,表示你希望从其中读取 JSON 数据的源。

  • 如果它是一个 String 对象,那么 readValue() 方法会将这个字符串视为包含 JSON 数据的源。例如:
String json = "{\"name\":\"Alice\",\"age\":25}";
Person person = objectMapper.readValue(json, Person.class);
  •  如果它是一个 byte[] 对象,那么 readValue() 方法会将这个字节数组视为包含 JSON 数据的源。例如:
如果它是一个 byte[] 对象,那么 readValue() 方法会将这个字节数组视为包含 JSON 数据的源。例如:
  • 如果它是一个 File 对象,那么 readValue() 方法会从这个文件中读取 JSON 数据。例如:
File file = new File("/path/to/your/file.json");
Person person = objectMapper.readValue(file, Person.class);
  • 如果它是一个 InputStream 对象,那么 readValue() 方法会从这个输入流中读取 JSON 数据。例如:
InputStream inputStream = new FileInputStream("/path/to/your/file.json");
Person person = objectMapper.readValue(inputStream, Person.class);
  • 如果它是一个 Reader 对象,那么 readValue() 方法会从这个 reader 中读取 JSON 数据。例如:
Reader reader = new FileReader("/path/to/your/file.json");
Person person = objectMapper.readValue(reader, Person.class);

2. 第二个参数,是你希望将 JSON 数据反序列化为的 Java 类型。这个类型可以是任何 Java 类型,只要它可以从 JSON 数据成功地反序列化。例如,你可以指定一个自定义的类,或者 ListMap 等集合类型:

// 假设 Person 是你的一个自定义类
Person person = objectMapper.readValue(json, Person.class);
// 或者,将 JSON 数据反序列化为一个 List
List<Person> people = objectMapper.readValue(json, new TypeReference<List<Person>>() {});

在这些例子中,我们从不同的源读取 JSON 数据,并将其反序列化为 Java 对象。

3. HttpServletResponse

核心方法:

方法描述
void setStatus(int sc)给响应设置状态码
void setHeader(String name,String value)设置一个header,如果存在覆盖value
void addHeader(String name,String value)添加一个带有给定的名称和值的 header. 如果 name 已经存在,不覆盖旧的值, 并列添加新的键值对
void setContentType(String type)设置被发送到客户端的响应的内容类型。
void setCharacterEncoding(String charset)设置被发送到客户端响应的字符编码 (例如utf-8)
void sendRedirect(String location)使用重定向位置URL发送临时重定向给客户端
PrintWriter getWriter()响应调用getWriter()方法会获取一个PrintWriter()实例对象,该实例对象用于处理字符流数据
OutputStream getOutputStream()返回一个ServletOutputStream()实例对象,,用于处理字符流数据或者二进制的字节流数据。

3.1 设置状态码  

@WebServlet("/state")
public class StateServlet 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("响应状态码是"+status);
        //返回tomcat自带的错误页面
        //resp.sendError(404);
    }
}

 3.2 ⾃动刷新

@WebServlet("/refresh")
public class RefreshServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //告诉浏览器1秒刷新一次
        resp.setHeader("Refresh", "1");
        resp.getWriter().println("" + LocalDateTime.now());
    }
}

 下面浏览器每秒钟会自动刷新一次:

 3.3 重定向

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

@WebServlet("/redirect")
public class RediractServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //实现重定向,让浏览器自动跳转到百度浏览器
        resp.setStatus(302);
        resp.setHeader("Location","https://www.baidu.com");
        //另一种更简单的重定向写法
        //resp.sendRedirect("https://www.baidu.com");
    }
}

输入敲下回车之后,跳转到百度主页

 fiddler抓包结果如下:

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

书生-w

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值