JavaWeb 学习笔记 3:Servlet

2023-09-19 09:00:00

JavaWeb 学习笔记 3:Servlet

1.简介

Servlet 是 JavaEE 定义的一套 Web 应用开发标准(接口),实现了该技术的 Web 服务器软件(如 Tomcat)上可以运行一个 Servlet 容器,只要我们使用 Servlet 技术开发 Web 应用,就可以打成 war 包后放在 Web 服务器上,Web 服务器软件可以自动解包,并执行其中 Servlet 相关的 API 实现类,以对外提供服务。

整个过程可以表示为:

1627234763207

2.快速开始

先按照上篇文章说的,创建一个 Maven Web 项目。

这里我使用 Maven 模板的方式创建项目:

mvn archetype:generate ^
-DgroupId=cn.icexmoon ^
-DartifactId=web-demo ^
-DarchetypeArtifactId=maven-archetype-webapp ^
-Dversion=0.0.1-snapshot ^
-DinteractiveMode=false

这里是 CMD 中执行的多行命令,因为分行符号的不同,不同的终端工具写法有所不同。

会自动生成一个 JUnit 依赖,不过版本太老(3.x),这里替换为 4.x:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>

添加 Tomcat 启动插件:

<plugins>
    <!--Tomcat插件 -->
    <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.2</version>
    </plugin>
</plugins>

添加 Servlet API 的依赖:

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>

创建一个类,并实现Servlet接口:

public class HelloServlet implements Servlet {
    public void init(ServletConfig servletConfig) throws ServletException {

    }

    public ServletConfig getServletConfig() {
        return null;
    }

    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("Hello World!");
    }

    public String getServletInfo() {
        return null;
    }

    public void destroy() {

    }
}

其中负责处理 HTTP 请求的是service方法,这里简单的在控制台打印一句话。

新版本的 Servlet 可以使用注解的方式指定请求路径:

@WebServlet("/hello")
public class HelloServlet implements Servlet {
	// ...
}

访问 http://localhost:8080/web-demo/hello,在服务端控制台可以看到输出。

3.Servlet 生命周期

Servlet 对象由 Web 服务器创建,其中实现的Servlet接口相关方法也由 Web 服务器在适当的时候执行。这些执行时机是和 Servlet 对象的生命周期相关的。

Servlet 对象有以下生命周期:

  • 实例创建:由 Web 服务器创建 Servlet 实例,默认为延迟创建(有相关的 HTTP 请求时才创建)。
  • 初始化:Servlet 对象创建后,Web 服务器会调用 Servlet.init()方法执行初始化工作,一般用于准备 Servlet 处理 HTTP 请求需要的资源等。
  • 处理请求:有 Servlet 相关的 HTTP 请求产生后,Web 服务器会调用Servlet.service()方法处理请求并返回处理结果(HTTP 响应报文)。
  • 销毁:当 Web 服务器(正常)关闭,或者 JVM 执行内存清理时,Web 服务器会调用Servlet.destroy()方法执行清理工作,主要包含对 Servlet 拥有资源的清理和关闭等。

3.1.init

默认情况下 init() 方法只会在第一次 HTTP 请求产生后被调用:

@WebServlet("/hello")
public class HelloServlet implements Servlet {
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("Servlet init...");
    }
    // ...
}

这是因为我们上边说过的,Servlet 实例创建的方式默认为延迟创建,而初始化是在创建之后执行。

可以将 Servlet 对象的创建方式修改为“急切创建”:

@WebServlet(value = "/hello", loadOnStartup = 1)
public class HelloServlet implements Servlet {
	// ...
}

属性loadOnStartup决定 Servlet 对象创建的时机:

  • 负数,延迟创建。在第一次相关的 HTTP 请求产生后创建 Servlet 对象。
  • 0 或正整数,急切创建。Web 服务器启动后立即创建 Servlet 对象,数字越小优先级越高。

3.2.service

在有相关的 HTTP 请求产生时被调用。负责处理 HTTP 请求,并生成响应报文。

public class HelloServlet implements Servlet {
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        System.out.println("Hello World!");
    }
}

3.3.destroy

Web 服务器(正常)关闭,或者 JVM 执行内存清理时被调用。

public class HelloServlet implements Servlet {
    public void destroy() {
        System.out.println("Servlet destroy...");
    }
    // ...
}

要注意的是,像直接终止 Tomcat 进程这种操作属于非正常关闭,Tomcat 是来不及执行正常的关闭流程的,也就不会执行 Servlet 的清理工作。

因此,要触发destroy方法,需要让 Tomcat 正常退出。其中的一个方法是在命令行下用 Maven 插件启动 Tomcat,并使用热键Ctrl+C退出程序:

D:\workspace\learn-javaweb\ch3\web-demo>mvn tomcat7:run
[INFO] Scanning for projects...
[INFO]
...
九月 08, 2023 11:33:53 上午 org.apache.coyote.AbstractProtocol start
信息: Starting ProtocolHandler ["http-bio-8080"]
Servlet destroy...
终止批处理操作吗(Y/N)? y

4.Servlet 抽象层级

4.1.请求分发

默认情况下,写在 Servlet.service()方法中的内容可以用于处理任意 Request Method 类型的 HTTP 请求。这通常是不合适的,通常我们需要对特定 Reuqest Method 的请求使用特定的处理。

比如,如果要对 http://localhost:8080/web-demo/hello 请求时的 POST 和 GET 方法执行不同的处理逻辑:

public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    HttpServletRequest request = (HttpServletRequest) servletRequest;
    switch (request.getMethod()){
        case "GET":
            System.out.println("From get request.");
            break;
        case "POST":
            System.out.println("From post request.");
            break;
        default:
    }
    System.out.println("Hello World!");
}

测试 POST 请求可以使用 HTTP 调试工具(比如 APIPost)。

4.2.模板模式

如果每个 Servlet 都要这样写,就很麻烦。可以使用模板模式的思想进行重构,抽离出一个 Servlet 的公共基类:

public abstract class HttpServletTemplate implements Servlet {
    @Override
    public void init(ServletConfig servletConfig) throws ServletException {

    }

    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    @Override
    public final void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
        if (!(servletRequest instanceof HttpServletRequest)
                || !(servletResponse instanceof HttpServletResponse)) {
            throw new RuntimeException("Servlet 请求和响应对象不是 HTTP 相关");
        }
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        switch (request.getMethod()){
            case "GET":
                this.doGet(request, response);
                break;
            case "POST":
                this.doPost(request, response);
                break;
            default:
        }
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) {

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) {

    }

    @Override
    public String getServletInfo() {
        return null;
    }

    @Override
    public void destroy() {

    }
}

这里有一些细节:

  • 模板类并不需要生成实例,所以被定义为抽象类(abstract)。
  • service方法负责按照 Request Method 类别进行分发,被分发后的方法(比如 doGet)需要被子类继承和重写,所以设置为protected。考虑到灵活性,这里没有将其设置为抽象方法,否则即使不用处理POST请求,也必须重写doPost方法。
  • 分发请求的service()方法不能被子类重写,所以设置为final

现在只需要很少的代码就可以实现之前的要求:

@WebServlet("/hello2")
public class Hello2Servlet extends HttpServletTemplate{
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
        System.out.println("From post request.");
        System.out.println("Hello World2!");
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
        System.out.println("From get request.");
        System.out.println("Hello World2!");
    }
}

可以按照需要覆盖doXXX方法。

4.3.HttpServlet

实际上这种抽象和重构的工作并不需要我们自己完成,Servlet 本身就提供类似的抽象:

image-20230908122518757

因此我们只需要继承HttpServlet就可以了:

@WebServlet("/hello3")
public class Hello3Servlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
        System.out.println("From get request.");
        System.out.println("Hello World3!");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
        System.out.println("From post request.");
        System.out.println("Hello World3!");
    }
}

5.urlPattern

Servlet 依赖于 @WebServlet注解的urlPatterns属性确定是否与请求路径(url)匹配。

@WebServletvalue属性是urlPatterns属性的别名。

5.1.精确匹配

常见的 urlPattern 都是采用精确匹配:

@WebServlet("/user/list")
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
        System.out.println("UserServlet...");
    }
}

此时只有请求路径是/user/list的 HTTP 请求才会被这个 Servlet 处理。

urlPattern 可以配置多个值,此时这些规则都可以用于匹配:

@WebServlet({"/user/list","/user/1"})

现在无论是/user/list这样的请求还是/user/1这样的请求都会被这个 Servlet 处理。

5.2.路径匹配

可以使用通配符(*)匹配某个路径下的请求,比如:

@WebServlet("/user/*")
public class User2Servlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
        System.out.println("User2Servlet...");
    }
}

示例中的 Servlet 可以处理任意以/user/开头的路径。

需要注意的是,是可以一个路径同时匹配多个 Servlet 的 urlPattern 的,比如目前这个示例中,/user/list这样的请求同时可以被UserServletUser2Servlet的规则匹配。此时精确匹配的优先级高于路径匹配,所以是UserServlet处理请求。

5.3.扩展名匹配

在 url 中同样可以使用扩展名作为请求结尾,比如:

http://localhost:8080/web-demo/book/list.do

可以用下面的 Servlet 匹配和处理:

@WebServlet("*.do")
public class User3Servlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
        System.out.println("User3Servlet...");
    }
}

需要注意的是,扩展名匹配前不能加路径分隔符/,比如:

@WebServlet("/*.do")

此时应用无法启动,会报错:

java.lang.IllegalArgumentException: Invalid <url-pattern> /*.do in servlet mapping...

5.4.任意匹配

可以用//*匹配任意路径的请求。

@WebServlet("/")
public class User4Servlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
        System.out.println("User4Servlet...");
    }
}

上面的示例可以匹配任意没有被其它 Servlet 匹配的请求。

可以同时配置//*匹配,但后者的优先级更高。此外/配置后会替换掉 Tomcat 默认的DefaultServlet,该 Servlet 负责处理对静态资源的请求。换言之,使用//*匹配路径可能导致无法访问静态资源。所以不推荐在项目中使用//*匹配路径。

最后,几种匹配模式的优先级是 精确匹配 > 目录匹配> 扩展名匹配 > /* > / 。

6.用 XML 配置 Servlet

Servlet 从 3.0 版本之后支持以注解的方式进行配置,在 3.0 版本之前需要在web.xml文件中配置。

添加一个不用注解配置的 Servlet:

public class User5Servlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doGet(req, resp);
        System.out.println("User5Servlet...");
    }
}

修改 web.xml

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <servlet>
    <servlet-name>user4servlet</servlet-name>
    <servlet-class>cn.icexmoon.webdemo.urlpattern.User4Servlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>user4servlet</servlet-name>
    <url-pattern>/user/4</url-pattern>
  </servlet-mapping>
</web-app>

和用注解配置的方式效果是相同的。

The End,谢谢阅读。

本文的完整示例可以从这里获取。

7.参考资料

更多推荐

ThinkPHP5,使用unionAll取出两个毫无相关字段表的数据且分页

一:首先来了解一下union和unionAll1:取结果的并集,是否去重union:对两个结果集进行并集操作,不包括重复行,相当于distinct,同时进行默认规则的排序;unionAll:对两个结果集进行并集操作,包括重复行,即所有的结果全部显示,不管是不是重复;2:获取结果后的操作,是否排序union:会对获取的结

基于Java+SpringBoot+Vue的旧物置换网站设计和实现

基于Java+SpringBoot+Vue的旧物置换网站设计和实现源码传送入口前言主要技术系统设计功能截图数据库设计代码论文目录订阅经典源码专栏Java项目精品实战案例《500套》源码获取源码传送入口前言摘要随着时代在一步一步在进步,旧物也成人们的烦恼,许多平台网站都在推广自已的产品像天猫、咸鱼、京东。所以开发出一套关

云计算(Docker)

Docker简介Docker是一个开源的应用容器引擎,基于Go语言,并遵从Apache2.0协议开源。它可以让开发者打包应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化。Docker可用于开发应用、交付应用、运行应用等场景。容器是完全使用沙箱机制,相互之间不会有任何接口

【ICCV 2023】FocalFormer3D : Focusing on Hard Instance for 3D Object Detection

原文链接:https://arxiv.org/abs/2308.045561.引言目前的3D目标检测方法没有显式地去考虑漏检问题。本文提出了困难实例探测(HIP)。受目标检测的级联解码头启发,HIP逐步探测误检样本,极大提高召回率。在每个阶段,HIP抑制TP样本并关注之前阶段的FN样本,则通过迭代HIP,可以处理困难的

ES6中新增加的Proxy对象及其使用方式

聚沙成塔·每天进步一点点⭐专栏简介⭐Proxy对象的基本概念Proxy对象的主要陷阱(Traps)⭐使用Proxy对象⭐写在最后⭐专栏简介前端入门之旅:探索Web开发的奇妙世界记得点击上方或者右侧链接订阅本专栏哦几何带你启航前端之旅欢迎来到前端入门之旅!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打

ffmpeg & ffplay

gif->jpg:```ffmpeg-i4.gif-r25-q:v4-pix_fmtyuv420pjpg2/frame%03d.jpg-y```#ffplay```ffplay[选项]['输入文件']```option```'-xwidth'强制以"width"宽度显示'-yheight'强制以"height"高度显示

【农业生产模拟】WOFOST模型与PCSE模型教程

查看原文>>>【农业生产模拟】WOFOST模型与PCSE模型实践目录第一章:理论基础农作物生长模型概述第二章:数据准备第三章:WOFOST模型基础第四章:PythonCropSimulationEnvironment第五章:案例拓展WOFOST(WorldFoodStudies)和PCSE(PythonCropSimu

多频超声波清洗机有什么特点?使用需要注意什么?

多频超声波清洗机是指具备多个频率可调的超声波发生器的清洗机,是在一只清洗槽内,安装有两种或三种以上不同频率的超声波换能器,由多只发生器分别推动各自频率的超声波进行清洗。传统的超声波清洗机通常只能在固定的频率下工作,而多频超声波清洗机具有更广泛的应用能力。梵英超声(fanyingsonic)是专业超声波设备制造商,推荐使

Springboot整合ElasticSearch(1)- 环境搭建 -非自动注入的方式

Springboot整合ElasticSearch(1)1、基本信息1、SpringBoot版本:2.7.14<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><ve

零基础Linux_6(开发工具_下)函数库链接+Makefile+实现进度条+Git

目录1.函数库(链接)1.1链接1.2动态库与静态库2.makefile2.1项目构建2.2Makefile的概念2.3Makefile的编写2.4.PHONY定义伪目标ACM时间3.实现进度条(缓冲区)3.1缓冲区的概念3.2实现一个简易"进度条"4.版本控制器Git4.1Git的概念和准备工作4.2git三板斧4.

AI写作宝-为什么要使用写作宝

写作一直是一项需要创造力和思考的任务,人工智能(AI)正逐渐成为我们写作过程中的一位新伙伴。AI写作宝等在线AI写作工具正日益普及,为我们提供了更多的写作选择和可能性。AI写作宝:什么是它们,以及它们能做什么?AI写作宝是一种基于人工智能技术的在线工具,旨在帮助用户生成各种类型的文字内容。它们可以根据用户输入的关键词、

热文推荐