JavaWeb 学习笔记 3:Servlet

news2024/11/24 17:10:16

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.参考资料

  • 黑马程序员JavaWeb基础教程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1021746.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Kubernetes二进制安装】

Kubernetes二进制安装 cluster-health&#xff1a;检查etcd集群的运行状况也就是说 kubelet 首次访问 API Server 时&#xff0c;是使用 token 做认证&#xff0c;通过后&#xff0c;Controller Manager 会为 kubelet 生成一个证书&#xff0c;以后的访问都是用证书做认证了。在…

RS485以及MODBUS学习

学习目的&#xff1a; 1、什么是485&#xff1f; 2、485如何通信&#xff1f; 3、如何使用熟能生巧&#xff1f; RS485是一种四总线通信&#xff0c;分别是VCC、GND、485_A、485_B。两根负责通信&#xff0c;两根负责进行供电。 RS485通信 硬件层&#xff1a;解决的是数据传输问…

【ELFK】之消息队列kafka

一、kafka的定义 Kafka 是一个分布式的基于发布/订阅模式的消息队列&#xff08;MQ&#xff0c;Message Queue&#xff09;&#xff0c;主要应用于大数据实时处理领域。Kafka 是最初由 Linkedin 公司开发&#xff0c;是一个分布式、支持分区的&#xff08;partition&#xff0…

Arduino程序设计(十)8×8 LED点阵显示(MAX7219)

88 LED点阵显示 前言一、MAX7219点阵模块二、点阵显示实验1、点阵显示部分图形2、点阵显示数字 0 ~ 9 及 26 个英文字母 总结 前言 本文主要介绍88 LED点阵显示实验&#xff0c;分别是&#xff1a;1、介绍MAX7219点阵模块&#xff1b;2、点阵显示部分图形&#xff1b;3、点阵显…

探索以太坊 Layer 2 解决方案的后起之秀——Starknet

作者: stellafootprint.network 数据来源: Starknet Dashboard “区块链三难题”&#xff0c;或“可扩展性三难题”&#xff0c;强调了区块链平台想要去平衡安全性、去中心化和可扩展性将面临的挑战。通常情况下&#xff0c;区块链架构只能有效地优先考虑其中两个难题。例如&…

蓝牙核心规范(V5.4)10.2-BLE 入门笔记之CIS篇

LE CIS 同步通信 同步通信提供了一种使用蓝牙LE在设备之间传输有时间限制的数据的方式。它提供了一个机制,允许多个接收器设备在不同的时间从相同的源接收数据,以同步它们对该数据的处理。LE AUDIO使用同步通信。 当使用同步通信时,数据具有有限的时间有效期,在到期时被认…

Nacos服务注册中心

1.引入依赖 <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency> 2. application.properties中配置 #-------------- Nacos配置 spring.cloud.nacos.…

项目开发过程中,客户提出了新的需求,怎么办?

由于用户已经签署用户需求报告&#xff0c;面对他们提出的新需求&#xff0c;要初步评估判断是否涉及相关需求的变更&#xff0c;作为项目经理&#xff0c;我会采取以下步骤&#xff1a; 1. 与客户进行沟通&#xff1a;首先&#xff0c;我会与客户进行进一步的沟通和讨论&…

【视觉SLAM入门】9.1 建图1---SLAM任务,稠密地图构建,立体视觉,RGBD,八叉树,点云地图等各种不同地图

"讷为君子&#xff0c;寡为吉人 ” 1. 立体稠密地图1.1 地图构建1.2 分析立体相机稠密建图效果 2. RGB-D稠密地图2.1 地图对比2.1.1 八叉树地图 3. 建图&#xff1f;定位&#xff1f;孰轻孰重3.1 鬼影问题3.2 三维重建 4. 总结 SLAM的功能&#xff1a; 直到现在我们可以知…

嵌入式:驱动开发 Day7

作业&#xff1a;基于GPIO子系统&#xff0c;编写LED的驱动程序和应用程序 驱动程序&#xff1a;myled.c #include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> #inc…

Python入门-pack和unpack的用法

struct.calcsize(format) 返回与格式字符串format相对应的结构体的大小&#xff08;以及由 生成的字节对象的大小 &#xff09; 使用大端顺序打包和解包三种不同大小的整数&#xff1a; from struct import *pack(">bhl", 1, 2, 3)unpack(>bhl, b\x01\x00\x…

聚焦企业开放OpenAPI痛难点,华为云API Explorer助力伙伴构建API门户

当前&#xff0c;IT研发的主流架构已从单体架构向微服务架构转型&#xff0c;带动了产品迭代速度和项目交付速度不断加快&#xff0c;随着整个IT产业规模的不断扩大&#xff0c;API数量也随之呈爆发式增长。据不完全统计&#xff0c;目前业界有70%到90%的业务是通过开源代码和第…

数据可视化 -- ECharts 入门

文章目录 引言1. ECharts的基本使用1.1 ECharts的快速上手1.2 相关配置讲解 2. ECharts常用图表2.1 图表1 柱状图2.1.1 柱状图的实现步骤2.1.2 柱状图的常见效果2.1.3 柱状图特点2.1.4 通用配置 2.2 图表2 折线图2.2.1 折线图的实现步骤2.2.2 折线图的常见效果2.2.3 折线图的特…

Java的Socket通信的断网重连的正确写法

Java的Socket通信的断网重连的正确写法 Socket通信的断网重连介绍客户端与服务端源码演示截图本地演示服务器演示演示截图 总结 Socket通信的断网重连介绍 针对于已经建立通信的客户端与服务器&#xff0c;当客户端与服务器因为网络问题导致网络不通而断开连接了或者由于服务器…

ET数据无效值处理(仅代表个人观点)

1将无效值去除查看&#xff0c;查看有效值 有效值范围为1830~9472 2.计算有效范围最大值最小值 如果计算四分数据得将栅格属性表导出execl进行计算 2根据该片文章进行替换 如何处理MODIS蒸散数据(ET)中的填充值&#xff08;仅作参考&#xff09;_Z_W_H_的博客-CSDN博客

大数据学习1.1-Centos8虚拟机安装

1.创建新的虚拟机 2.选择稍后安装OS 3.选择Linux的CentOS8 4.选择安装路径 5.分配20g存储空间 6.自定义硬件 7.分配2g内存 8.分配2核处理器 9.选择镜像位置 10.开启虚拟机安装 推荐密码设置为root

TS开发环境搭建

一、安装Node.js 官网&#xff1a;Node.js (nodejs.org) 可以去看别的大佬安装Node.js的文章&#xff0c;因为大部分人都安装了Node环境&#xff0c;这里我就不做说明 二、使用npm全局安装typescript 在终端中输入如下命令&#xff1a; npm i -g typescript 安装完成会有如…

程序开发:在线报名线下活动小程序源码功能解析

针对线下活动在线报名场景的小程序&#xff0c;支持在线支付费用以及线下核销。 可以应多大多数的线下报名&#xff0c;线上报名客服表单可以定制订单&#xff0c;支持导出报名&#xff0c;支持审核&#xff0c;支持分享&#xff0c;支持分销拓客以及线下核销。 WEB小程序双端…

2023年日经225指数研究报告

第一章 指数概况 1.1 概述 日经225指数&#xff0c;又被称为日经平均股票价格或日经225平均指数&#xff0c;通常被称为日经或日经指数&#xff0c;是东京证券交易所的一个重要股价指数。该指数自1950年起由日本经济新闻&#xff08;The Nikkei&#xff09;日报计算&#xff…

Chrome浏览器崩溃“STATUS_INVALID_IMAGE_HASH”的解决方法

原文地址&#xff1a;Chrome浏览器崩溃"STATUS_INVALID_IMAGE_HASH"的解决方法 | JUNES BLOG | 六月博客 今天无意间&#xff0c;发现Chrome浏览器多了个“ 由贵单位管理 ”的提示&#xff0c;出于安全的考虑&#xff0c;想立马把它搞掉&#xff0c;百度了一堆方法&a…