【JavaEE】_Servlet API

目录

[1. HttpServlet](#1. HttpServlet)

[1.1 init方法](#1.1 init方法)

[1.2 destroy方法](#1.2 destroy方法)

[1.3 service方法](#1.3 service方法)

[1.4 Servlet的生命周期](#1.4 Servlet的生命周期)

[1.5 代码示例](#1.5 代码示例)

[1.5.1 使用postman构造请求](#1.5.1 使用postman构造请求)

[1.5.2 使用ajax构造请求](#1.5.2 使用ajax构造请求)

[2. HttpServletRequest](#2. HttpServletRequest)

[2.1 核心方法](#2.1 核心方法)

[2.2 代码示例1:打印请求信息](#2.2 代码示例1:打印请求信息)

[3. 前端给后端传参](#3. 前端给后端传参)

[3.1 通过GET的query string](#3.1 通过GET的query string)

[3.2 通过POST,借助form表单](#3.2 通过POST,借助form表单)

[3.3 通过POST,使用json格式构造body](#3.3 通过POST,使用json格式构造body)

[4. HttpServletResponse](#4. HttpServletResponse)


[便捷起见,非必要时Servlet程序均以smart tomcat方式部署程序]

Servlet有3个重要类,分别为HttpServlet,HttpServletRequest,HttpServletResponse;

1. HttpServlet

编写Servlet程序第一步就是创建一个类继承自HttpServlet类并重写其doGet方法;

其核心方法有:

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

1.1 init方法

  1. 不由程序员手动调用,由tomcat自动调用

  2. 当tomcat首次收到了和该类相关联的请求时,才会进行实例化

以上一篇用Servlet的hello world程序为例:

WebServlet注解就是将/hello类和HelloServlet类绑定在一起,表示:

如果Tomcat收到了/hello这样路径的请求,就会调用HelloServlet,于是就对HelloServlet进行实例化;

实例化只进行一次,后续再收到/hello,就不必再重复实例化了,直接复用之前的HelloServlet实例即可;

  1. 可以重写init方法,插入一些我们自己的初始化的逻辑:

运行以下代码:

java 复制代码
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    public void init() throws ServletException {
        // 重写init方法
        System.out.println("init");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//        把数据显示在服务器控制台
        System.out.println("Hello world");
//        把数据写回浏览器
        resp.getWriter().write("Hello world");
    }
}

(1)运行程序,仅启动服务器时,在控制台并未打印init:

原因:只有当请求关联到hello路径时才会打印;

(2)在浏览器中打开页面,即触发了请求,此时服务器的日志上就会调用init进行实例化:

(3)多次刷新页面,即多次发送请求:

doGet方法多次被调用,而init方法仅被调用一次;

  1. 也可以通过修改web.xml配置让tomcat启动时立即实例化该servlet;

  2. servlet是服务器上运行的代码,只要服务器不重新启动,init就不会再执行

1.2 destroy方法

  1. 可以使用destroy方法进行一些清理工作

  2. 只要服务器在运行,都有可能再使用,服务器终止时就不可再使用了;

  3. 重写destroy方法:

java 复制代码
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    public void init() throws ServletException {
        // 重写init方法
        System.out.println("init");
    }

    @Override
    public void destroy() {
//        重写destroy方法
        System.out.println("destroy");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//        把数据显示在服务器控制台
        System.out.println("Hello world");
//        把数据写回浏览器
        resp.getWriter().write("Hello world");
    }
}

启动服务器并刷新页面后,点击停止后,可见服务器日志调用了destroy方法:

注:但此处的destroy方法是否能被执行到并不确定:

第一种情况:如果是使用smart tomcat的停止按钮终止程序,这个操作本质上是通过tomcat的8005端口来主动停止,可以触发destroy方法;

第二种情况:如果是直接杀进程,此时可能就来不及执行destroy方法;

故而并不推荐使用destroy方法;

1.3 service方法

当收到路径匹配的HTTP请求就会触发service方法

比如doGet方法就是在service方法中调用的:

父类HttpServlet有一个service方法,其内部就会调用doGet方法;

1.4 Servlet的生命周期

开始时,执行 init;

每次收到请求,执行 service;

销毁前执行 destroy;

注:一个servlet程序包含很多个servlet,某个servlet的生死,不影响整个sevlet程序;

1.5 代码示例

在java目录下再创建一个类:MethodServlet;

其内容如下:

java 复制代码
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 MethodServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doGet");
        resp.getWriter().write("doGet");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doPost");
        resp.getWriter().write("doPost");
    }

    @Override
    protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doPut");
        resp.getWriter().write("doPut");
    }

    @Override
    protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("doDelete");
        resp.getWriter().write("doDelete");
    }
}

直接在浏览器输入地址访问,只能发送GET请求,其他请求类型可以通过ajax或postman进行构造。

1.5.1 使用postman构造请求

基于一次浏览器地址访问后(即已构造一个GET请求),使用postman依次构造:GET,POST,PUT,DELETE请求并发送,再查看服务器日志:

1.5.2 使用ajax构造请求

  1. 在IDEA中创建.html文件:

tomcat要求:.html文件设置在webapp目录下,与WEB-INF同级

可以使用vscode进行编写:

用vscode打开即可;

  1. 编写代码:
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>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
    <script>
        $.ajax({
            type:'get',
            url:'method',
            // 此处url为相对路径,此时基准路径为当前html所在的路径:127.0.0.1:8080/hello_servlet
            success:function(body, status){
                console.log(body);
            }
        })
    </script>
</body>
</html>

在浏览器输入地址:

按f12进入chrome的console标签页:

同样在服务器日志也可见:

  1. 依次修改请求类型为post,put,delete,再运行,即可得到不同的结果:

注:使用ajax构造HTTP请求的注意点:

(1)html文件的位置:webapp下,与WEB-INF同级;

(2)在html文件中使用ajax构造HTTP请求时的url可以写为相对路径,该相对路径的基准路径就是当前html文件的路径,在此例中为:127.0.0.1:8080/hello_servlet;

也可以写为绝对路径:(浏览器要求:以 / 开头的为绝对路径

html 复制代码
        $.ajax({
            type:'get',
            url:'/hello_servlet/method',
            success:function(body, status){
                console.log(body);
            }
        })

(3)注意@WebServlet注解的路径必须 / 开头,此处的含义不是绝对路径,而是servlet的要求;

2. HttpServletRequest

HttpServletRequest表示一个HTTP请求,这个对象是tomcat自动构造的,

tomcat其实会自动监听端口,接受连接,读取请求,解析请求,构造请求对象等一系列工作;

2.1 核心方法

|--------------------------------------------|-------------------------------------------|
| 方法 | 描述 |
| String getProtocal() | 返回请求协议的名称和版本 |
| String getMethod() | 返回请求的HTTP方法的名称,如GET、POST或PUT |
| String getQequestURI() | 从协议名称直到HTTP请求的第一行的查询字符串中,返回该请求的URL的一部分 |
| String getContexPath() | 返回指示请求上下文的请求URI部分 |
| String getQueryString() | 返回包含在路径后的请求URL中的查询字符串(?后的参数) |
| Enumeration getParameterNames() | 返回一个String对象的枚举,包含在该请求中包含的参数的名称 |
| String getParameter(String name) | 以字符串形式返回请求参数的值,或者如果参数不存在则返回null |
| String[] getParameterValues(String name) | 返回一个字符串对象的数组,包含所有给定的请求参数的值,如果参数不存在则返回null |
| Enumeration getHeaderNames() | 返回一个枚举,包含在该请求中包含的所有头名 |
| String getHeader(String name) | 以字符串形式返回指定的请求头的值 |
| String getCharacter Encoding() | 返回请求主题中使用的字符编码的名称 |
| String getContentType() | 返回请求主题的MIME类型,如果不知道类型则返回null |
| int getContentLength() | 以字节为单位返回请求主体的长度,并提供输入流,或者如果长度未知则返回-1 |
| InputStream getInputStream() | 用于读取请求的body内容,返回一个InputStream对象 |

注:(1)query string是键值对结构,此处可以通过getParameter方法来根据key获取到value

(2)InputStream就是输入流对象,进行read操作即可把body数据读取出来;

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

基于hello_servlet项目,创建一个ShowRequestServlet.java文件,其内容如下:

java 复制代码
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 {
        // 设置响应的content-type,即表明body里的数据格式是何种类型
        resp.setContentType("text/html");
        // 创建一个StringBuilder,把这些api的结果拼起来统一写回响应中
        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>");
        stringBuilder.append("<br>");
        stringBuilder.append("<br>");

        // 获取到header中的所有键值对
        Enumeration<String> headerNames = req.getHeaderNames();
        while(headerNames.hasMoreElements()){
            String headerName = headerNames.nextElement();
            stringBuilder.append(headerName+" : "+req.getHeader(headerName));
            stringBuilder.append("<br>");
        }

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

运行后根据url打开页面:

注:(1)此处实现html页面的换行操作不能使用\n,而使用<br>标签,同时设置响应的content-type为text/html,告诉浏览器,响应的body里的数据格式是何种类型;

(2)按照没有添加query string时,默认响应为null,比如增加query string 为a=10&b=20,响应的query string便为a=10&b=20;

3. 前端给后端传参

3.1 通过GET的query string

(1)前端:test.html:

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>
    <form action="postParameter" method="post">
        <input type="text" name="studentId">
        <input type="text" name="classId">
        <input type="submit" value="提交">
    </form>

    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
    <script>
    </script>
</body>
</html>

(2)后端:GetParameterServlet.java:

java 复制代码
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 {
        // 预期浏览器会发一个形如 /getParameter?studentId=001&classId=2001;
        // 借助req的getParameter方法就能拿到query string 中的键值对内容了
        // getParameter得到的是一个String类型的值
        String studentId = req.getParameter("studentId");
        String classId = req.getParameter("classId");
        resp.setContentType("text/html");
        resp.getWriter().write("studentId = "+studentId +" classId = "+classId);
    }
}

运行程序后,根据url打开浏览器页面如下:

注:(1)输入在url后的查询字符串键值对会被Tomcat处理成形如Map这样的结构,后续就可以随时通过key来获取到value了;

(2)如果key在query string中不存在,则返回值为空;

3.2 通过POST,借助form表单

form表单也是键值对形式组织数据的,只是这部分内容在body中;

对于前端是form表单这样格式的数据,后端还是使用paraMeter来获取;

(1)前端:test.html:

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>
    <form action="postParameter" method="post">
        <input type="text" name="studentId">
        <input type="text" name="classId">
        <input type="submit" value="提交">
    </form>

    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
    <script>
    </script>
</body>
</html>

(2)后端:PostParameterSrvlet.java:

java 复制代码
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 PostParameterServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String studentId = req.getParameter("studentId");
        String classId = req.getParameter("classId");
        resp.setContentType("text/html");
        resp.getWriter().write("studentId = "+studentId+" classId = "+classId);
    }
}

运行程序后根据url打开浏览器页面:

提交后,页面如下:

注:(1)使用query string和使用form表单的后端代码格式基本相同,都是通过getParameter根据key获取value,只是使用form表单构造的键值对不在query string中,而是在body中;

(2)getParameter方法不仅能获取到请求中query string中的键值对,还可以获取到form表单构造的body中的键值对;

(3)以form表单形式构造并发送的请求一定会触发页面跳转;

(4).html及请求、浏览器页面的对应关系图:

(5)前端后端交互的过程:

图1:

图2:

3.3 通过POST,使用json格式构造body

json也是键值对结构的数据格式,可以把body按照该形式组织;

在前端,可以通过ajax方式构造该内容,也可以使用postman;

(1)postman构造请求:

(2)后端:PostParameter2Sevlet.java

java 复制代码
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("/postParameter2")
public class PostParameter2Servlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 通过这个方法处理 body 为 json 格式的数据
        // 直接把 req 对象中的 body 完整地读取出来

        // 获取请求的body长度并构造数组
        int length = req.getContentLength();
        byte[] buffer = new byte[length];

        InputStream inputStream = req.getInputStream();
        inputStream.read(buffer);

        //把这个字节数组构造成 String, 打印出来
        String body = new String(buffer, 0, length,"utf8");
        System.out.println("body = "+body);
        resp.setContentType("text/html");
        resp.getWriter().write(body);
    }
}

使用fiddler可以查看到构造的json请求:

当请求到达tomcat后,tomcat就会将其解析为req对象;

在servlet代码中,req.getInputStrea,,读取body内容,又把body的内容构造成一个响应结果返回给浏览器(此例中为postman),在post页面中就有响应结果:

注:(1)使用form表单构造请求传参与json格式请求传参的代码执行流程是类似的,只是传输数据的格式不同,

form表单的格式形如:

json格式形如:

但是当前通过json传递数据,服务器只是将整个body都读出来,没有按照键值对的方式处理,还不能根据key获取到value,可以使用第三方库来解析json的body,如jackson,gson,fastjson,由于spring mvc内置了jackson这个库,故而天然就支持处理jackson的数据。

故而,以引入jackson库为例:

通过maven引入第三方库:

基于jackson库,修改代码使得获取到请求body内部的键值对:

java 复制代码
 class Student{
    public int studentId;
    public int classId;
}
@WebServlet("/postParameter2")
public class PostParameter2Servlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

// 使用jackson的核心对象:
  ObjectMapper objectMapper = new ObjectMapper();

// readValue就是把一个json格式的字符串转为java对象:
  Student student = objectMapper.readValue(req.getInputStream(), Student.class);

    }
}

注意:(1)objectMapper对象的readValue方法的作用:

① 将json格式的字符串读取出来;

② 根据第二个参数类对象创建Student实例,第二个参数是一个类对象

③ 解析json格式的字符串,处理成map键值对结构;

④ 遍历所有键值对,看键的名字和Student实例的哪个属性名匹配,就把对应的value设置到该属性中;

⑤ 返回该Student实例;

(2)readValue方法用于将json字符串转成java对象,writeValue方法用于把一个java对象转成json格式字符串

(3)在上文代码中,将Student类内的属性设置为public修饰,这在Java中并不常见。但是如果要把一个类作为jackson返回的对象,就需要让jackson能够看到类中的属性,可以采取的方法有:

① 把属性设置为public;② 给该属性提供public修饰的getter与setter方法;

4. HttpServletResponse

servlet代码种的doXXX方法就是根据请求计算响应后,将响应的数据设置到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中写入二进制格式数据 |

(1)一个HTTP响应中报头的key是可以存在多个重复的

(2)可以通过setCharacterEncoding指定响应的编码格式:

浏览器默认不知道程序员的页面编码方式,会采取猜测的方式使用字符集进行解析,比如在getParameterServlet.java中将写回浏览器的响应格式写为:

java 复制代码
resp.setContentType("text/html");
resp.getWriter().write("学生id: "+studentId +" 班级id: "+classId);

此时根据路径打开浏览器页面,就会出现乱码:

为了避免这种情况,我们需要在写回响应前,显式指定响应的编码格式字符集:

java 复制代码
        resp.setContentType("text/html");
        resp.setCharacterEncoding("utf-8");
        resp.getWriter().write("学生id: "+studentId +" 班级id: "+classId);

刷新浏览器页面,有:

(3)也可以把字符集和ContentType一起设置:

java 复制代码
        resp.setContentType("text/html; charset=utf8");
        resp.getWriter().write("学生id: "+studentId +" 班级id: "+classId);

(4)void sendRedirect用于构造重定向响应,3xxx的状态码就会跳转到另外一个页面:

java 复制代码
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.sendRedirect("https://www.sogou.com");
    }
}

运行代码后,根据路径打开浏览器页面,即可跳转至搜狗主页:

前后端交互逻辑如下:

实现重定向,尤其是返回错误页面有多种方式:

相关推荐
BillKu37 分钟前
Java + Spring Boot + Mybatis 插入数据后,获取自增 id 的方法
java·tomcat·mybatis
全栈凯哥38 分钟前
Java详解LeetCode 热题 100(26):LeetCode 142. 环形链表 II(Linked List Cycle II)详解
java·算法·leetcode·链表
chxii39 分钟前
12.7Swing控件6 JList
java
全栈凯哥41 分钟前
Java详解LeetCode 热题 100(27):LeetCode 21. 合并两个有序链表(Merge Two Sorted Lists)详解
java·算法·leetcode·链表
YuTaoShao41 分钟前
Java八股文——集合「List篇」
java·开发语言·list
PypYCCcccCc1 小时前
支付系统架构图
java·网络·金融·系统架构
持续前进的奋斗鸭1 小时前
Postman测试学习(1)
学习·postman
华科云商xiao徐1 小时前
Java HttpClient实现简单网络爬虫
java·爬虫
扎瓦1 小时前
ThreadLocal 线程变量
java·后端
BillKu2 小时前
Java后端检查空条件查询
java·开发语言