JavaWeb 学习笔记 3:Servlet

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 {@Overridepublic void init(ServletConfig servletConfig) throws ServletException {}@Overridepublic ServletConfig getServletConfig() {return null;}@Overridepublic 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) {}@Overridepublic String getServletInfo() {return null;}@Overridepublic void destroy() {}
}

这里有一些细节:

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

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

@WebServlet("/hello2")
public class Hello2Servlet extends HttpServletTemplate{@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) {System.out.println("From post request.");System.out.println("Hello World2!");}@Overrideprotected 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 {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {super.doGet(req, resp);System.out.println("From get request.");System.out.println("Hello World3!");}@Overrideprotected 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 {@Overrideprotected 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 {@Overrideprotected 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 {@Overrideprotected 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 {@Overrideprotected 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 {@Overrideprotected 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.mzph.cn/news/82193.shtml

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

相关文章

Python 移动文件到指定路径

需求&#xff1a;将指定的文件从指定目录移动到用户指定的目标目录。 shutil 是 Python 标准库中的一个模块&#xff0c;它提供了许多文件和文件集合的高级操作。基本上&#xff0c;它可以帮助我们执行文件操作&#xff0c;例如复制、移动、更名和删除。它旨在与 os 模块一起使…

【测试开发】基础篇 · 专业术语 · 软件测试生命周期 · bug的描述 · bug的级别 · bug的生命周期 · 处理争执

【测试开发】基础篇 文章目录 【测试开发】基础篇1. 软件测试生命周期1.1 软件生命周期1.2 软件测试生命周期 2. 描述bug3. 如何定义bug的级别3.1 为什么要对bug进行级别划分3.2 bug的一些常见级别 4. bug的生命周期5. 产生争执这么怎么办&#xff08;处理人际关系&#xff09;…

ChatGPT:URL编码问题——如何正确进行URL编码以处理特殊字符

ChatGPT&#xff1a;URL编码问题——如何正确进行URL编码以处理特殊字符 报错&#xff1a; URISyntaxException: Malformed escape pair at index 192: http://Center/Question/questionList.html?seaKey%E6%8D%AE%E7%BB%9F%E8%AE%A1%EF%BC%8C%E5%9B%A0%E7%81%AB%E7%81%BE%E6%…

服务器数据恢复-LINUX操作系统下各文件系统误删除/格式化数据的恢复方案

服务器数据恢复环境&#xff1a; 基于EXT2/EXT3/EXT4/Reiserfs/Xfs文件系统的Linux操作系统。 服务器故障&#xff1a; LINUX操作系统下误删除/格式化数据。 服务器数据恢复过程&#xff1a; 1、首先会检测服务器是否存在硬件故障&#xff0c;如果检测出硬件故障&#xff0c;交…

Linux之jar包之启动与停止脚本

Linux之jar包之启动与停止脚本 一、使用说明二、脚本代码 一、使用说明 启动 ./service.sh start关闭 ./service.sh stop二、脚本代码 #!/bin/bash #这里可替换为你自己的执行程序&#xff0c;其他代码无需更改 APP_NAMEmxy_system-0.0.1-SNAPSHOT.jar # shellcheck disabl…

9.18算法

机器人重物1126 注意编号是方块的&#xff0c;而不是格点的 及如果为n*m的矩阵&#xff0c;需要开(n1)*(m1)的矩阵 //如果没有转向&#xff0c;就是走迷宫&#xff0c;结合记忆化&#xff0c;如果这个点之前走过就不走了 //又转向的话&#xff0c;就用一个变量记录当前转向&…

量化分析革新金融服务软件的三种方式

金融服务软件行业爱死量化分析了。 为什么呢&#xff1f;因为在这个本质上不可预测的行业中&#xff0c;量化分析提供了一种确定性&#xff0c;或者至少是类似于确定性的东西。 市场总是在变动&#xff0c;利润也起伏不定。交易达成了&#xff0c;然后落空&#xff0c;又再次…

Golang Linux 安装与环境变量配置

下载 Go 二进制包 wget https://dl.google.com/go/go1.21.1.linux-amd64.tar.gz 解压文件并将其移至 /usr/local 目录 sudo tar -C /usr/local -xzf go1.20.1.linux-amd64.tar.gz -C 选项解压文件到 /usr/local 目录&#xff0c;查看 /usr/local/go 目录的内容 将 Go 二进…

19 视图定义 union 是根据第一个 select 字段列表顺序,来进行 merge 的

前言 这个问题主要是 在之前存在这样的一个问题, 在生产环境上面 按照 我的直观理解, mysql 应该是根据 key 进行 merge, 所以 select 的顺序应该是 “不重要”??, 但是 结果我理解错了 然后 线上的查询也出现了问题, 发现很奇怪的问题, 明明 key01 列 是 id, 但是有一部…

深度学习——线性神经网络一

深度学习——线性神经网络一 文章目录 前言一、线性回归1.1. 线性回归的基本元素1.1.1. 线性模型1.1.2. 损失函数1.1.3. 解析解1.1.4. 随机梯度下降1.1.5. 用模型进行预测 1.2. 向量化加速1.3. 正态分布与平方损失1.4. 从线性回归到深度网络 二、线性回归的从零开始实现2.1. 生…

Java面试题整理(带答案)

目录 TCP和UDP的区别 get和post的区别 Cookie和session的区别 Java的基本类型有哪些&#xff1f; 抽象类和接口区别&#xff1f; 对于堆栈的理解 和equals区别 如何理解Java多态&#xff1f; 创建线程都有哪些方式 脏读、不可重复度、幻读都是什么&#xff1f; Jav…

数据治理-数据仓库环境

数据仓库环境包括一系列组织起来以满足企业需求的架构组件&#xff0c;从源系统流动到数据暂存区&#xff0c;数据可以在这里被清晰&#xff0c;当数据集成并存储在数据仓库或操作数据存储中时&#xff0c;可以对其进行补充丰富。在数据仓库中&#xff0c;可以通过数据集市或数…

多线程详解(上)

文章目录 一、线程的概念1&#xff09;线程是什么2&#xff09;为甚要有线程&#xff08;1&#xff09;“并发编程”成为“刚需”&#xff08;2&#xff09;在并发编程中, 线程比进程更轻量. 3&#xff09;线程和进程的区别 二、Thread的使用1&#xff09;线程的创建继承Thread…

[deeplearning]pytorch实现softmax多分类问题预测训练

写在前面&#xff1a;俺这两天也是刚刚加入实验室&#xff0c;因为之前的学习过程中用到更多的框架是tensorflow&#xff0c;所以突然上手pytorch多少有些力不从心了。 这两个框架的主要区别在与tensorflow更偏向于工业使用&#xff0c;所以里面的很多函数和类都已经封装得很完…

算法通关村-----链表中环的问题

环形链表 问题描述 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中…

Reactor 第十二篇 WebFlux集成PostgreSQL

1 引言 在现代的应用开发中&#xff0c;数据库是存储和管理数据的关键组件。PostgreSQL 是一种强大的开源关系型数据库&#xff0c;而 WebFlux 是 Spring 框架提供的响应式编程模型。本文将介绍如何使用 Reactor 和 WebFlux 集成 PostgreSQL&#xff0c;实现响应式的数据库访问…

【chrome扩展开发】消息通讯之onMessage消息监听

前言 chrome.runtime.onMessage.addListener 是 Chrome 扩展程序中用于监听其他模块发送的消息并做出响应的 API 当从扩展进程 (by runtime.sendMessage) 或内容脚本 (by tabs.sendMessage)发送消息时触发 语法 chrome.runtime.onMessage.addListener(callback: function, )ca…

使用Scrapy构建高效的网络爬虫

&#x1f482; 个人网站:【工具大全】【游戏大全】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 寻找学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 Scrapy是一个强大的Pyth…

python虚拟环境(venv)

一、什么是python环境 首先要知道什么是python环境&#xff1f; Python环境主要包括以下内容&#xff1a; 解释器 python.exe (python interpreter&#xff0c;使用的哪个解释看环境配置) Lib目录 标准库 第三方库&#xff1a;site-pakages目录&#xff0c;默认安装第三方…

题目:2859.计算 K 置位下标对应和

​​题目来源&#xff1a; leetcode题目&#xff0c;网址&#xff1a;2859. 计算 K 置位下标对应元素的和 - 力扣&#xff08;LeetCode&#xff09; 解题思路&#xff1a; 逐个计算下标是否符合要求即可。 解题代码&#xff1a; class Solution {public int sumIndicesWithK…