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

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

前后端交互逻辑如下:

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

相关推荐
魔道不误砍柴功2 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2342 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨2 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
安冬的码畜日常3 小时前
【玩转 Postman 接口测试与开发2_006】第六章:Postman 测试脚本的创建(中):脚本的位置与执行顺序、AI助手及私有模块的使用
测试工具·postman·测试脚本·postbot·package library
测开小菜鸟3 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity4 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天4 小时前
java的threadlocal为何内存泄漏
java
caridle5 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^5 小时前
数据库连接池的创建
java·开发语言·数据库
苹果醋35 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx