Servlet :生命周期、配置与实战
2026/8/2 6:50:30 网站建设 项目流程

一、Servlet 概述

1.1 JavaWeb 的三大组件

JavaWeb 开发中有三大核心组件,它们是构建 Java Web 应用的基石:

组件一:Servlet
作用:处理客户端请求,生成动态响应内容。是 JavaWeb 最基础的组件,必须 100% 掌握。

组件二:Filter
作用:在请求到达 Servlet 之前或响应返回客户端之后,对请求和响应进行预处理和后处理。例如字符编码过滤、登录校验、日志记录等。

组件三:Listener
作用:基于观察者模式,监听 Web 应用中的事件(如 ServletContext 创建、销毁,Session 创建、销毁,属性变化等),在事件发生时执行特定的业务逻辑。

1.2 Servlet 的作用

Servlet,即 Server + Let,意为“服务器端的小程序”。它的核心作用就是处理用户请求。

当客户端(浏览器)发出一个 HTTP 请求时,Tomcat(或其他 Servlet 容器)会根据请求的 URL 找到对应的 Servlet 来处理这个请求。

具体场景举例:
用户发出登录请求 → 由 LoginServlet 处理
用户发出注册请求 → 由 RegisterServlet 处理
用户发出查询商品列表请求 → 由 ProductListServlet 处理

也就是说,Servlet 是 MVC 模式中的 Controller(控制器)层,负责接收请求、调用业务逻辑、返回响应。

1.3 Servlet 与 Tomcat 的关系

很多初学者会混淆 Servlet 和 Tomcat 的关系,这里做一个清晰说明:

Tomcat 是一个 Servlet 容器(也叫 Web 服务器),它的主要职责是:
管理 Servlet 的生命周期(创建、初始化、调用、销毁)
将客户端的 HTTP 请求解析并转发给对应的 Servlet
接收 Servlet 的响应并返回给客户端

而 Servlet 是我们开发者编写的 Java 类,它实现了 javax.servlet.Servlet 接口,用来处理具体的业务逻辑。

Tomcat 负责“管”,Servlet 负责“做”。Tomcat 是舞台,Servlet 是演员。


二、第一个 Servlet

2.1 Servlet 接口详解

所有 Servlet 都必须实现 javax.servlet.Servlet 接口。该接口定义了以下 5 个方法,其中前三个是生命周期方法:

方法一:void init(ServletConfig config)
当 Tomcat 创建 Servlet 实例后,会立即调用 init() 方法。
该方法在整个生命周期中只被调用一次。
用于执行 Servlet 的初始化工作,例如加载配置文件、建立数据库连接等。

方法二:void service(ServletRequest request, ServletResponse response)
每次客户端请求到达时,Tomcat 都会调用 service() 方法。
该方法会被多次调用(每来一次请求就调用一次)。
用于处理请求并生成响应,是 Servlet 的核心业务方法。

方法三:void destroy()
当 Tomcat 要销毁 Servlet 实例时(通常是在服务器关闭或应用卸载时),会先调用 destroy() 方法。
该方法在整个生命周期中只被调用一次。
用于释放资源,例如关闭数据库连接、停止后台线程等。

方法四:ServletConfig getServletConfig()
返回当前 Servlet 的配置信息对象。
通常在 init() 方法中保存传入的 ServletConfig 对象,然后在本方法中返回。

方法五:String getServletInfo()
返回一个描述当前 Servlet 的字符串,基本用不上。

2.2 创建第一个 Servlet

第一步:编写 Servlet 实现类

package cn.tx.servlet; import javax.servlet.*; import java.io.IOException; public class Servlet1 implements Servlet { @Override public void init(ServletConfig servletConfig) throws ServletException { System.out.println("Servlet 初始化完成"); } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { System.out.println("处理请求中..."); servletResponse.getWriter().write("Hello Servlet!"); } @Override public String getServletInfo() { return null; } @Override public void destroy() { System.out.println("Servlet 即将销毁"); } }

第二步:在 web.xml 中配置 Servlet

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <!-- 声明 Servlet --> <servlet> <servlet-name>hello</servlet-name> <servlet-class>cn.tx.servlet.Servlet1</servlet-class> </servlet> <!-- 映射 Servlet 的访问路径 --> <servlet-mapping> <servlet-name>hello</servlet-name> <url-pattern>/hello</url-pattern> </servlet-mapping> </web-app>

配置说明:
<servlet-name>:为 Servlet 起一个别名,在项目内必须唯一。
<servlet-class>:Servlet 类的全限定名(包名 + 类名)。
<url-pattern>:客户端访问该 Servlet 的 URL 路径(相对于项目上下文路径)。

2.3 web.xml 配置详解

web.xml 是 JavaWeb 应用的部署描述符,位于 WEB-INF 目录下。它告诉 Tomcat 这个 Web 应用有哪些 Servlet、Filter、Listener,以及它们的映射关系。

重要规则:
<servlet-name> 在 <servlet> 和 <servlet-mapping> 中必须完全一致。
<url-pattern> 必须以 "/" 开头(除非是扩展名匹配)。
一个 Servlet 可以有多个 <url-pattern>,即多个路径映射到同一个 Servlet。

三、Servlet 生命周期

3.1 生命周期三阶段

Servlet 的生命周期由 Tomcat 管理,包含三个阶段:

阶段一:实例化与初始化
时机:第一次访问该 Servlet 时(或服务器启动时,若配置了 load-on-startup)。
动作:Tomcat 通过反射创建 Servlet 实例 → 调用 init() 方法。
次数:整个生命周期中只执行一次。

阶段二:服务
时机:每次客户端请求到达时。
动作:Tomcat 调用 Servlet 的 service() 方法。
次数:每来一次请求就执行一次,可执行多次。

阶段三:销毁
时机:服务器关闭或 Web 应用被卸载时。
动作:Tomcat 先调用 destroy() 方法,再销毁 Servlet 实例。
次数:整个生命周期中只执行一次。

3.2 生命周期完整演示代码

为了清晰地观察生命周期方法被调用的时机,我们可以编写如下测试代码:

package cn.tx.servlet; import javax.servlet.*; import java.io.IOException; public class LifecycleServlet implements Servlet { public LifecycleServlet() { System.out.println("1. 构造方法被调用(实例化)"); } @Override public void init(ServletConfig servletConfig) throws ServletException { System.out.println("2. init() 方法被调用(初始化)"); } @Override public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { System.out.println("3. service() 方法被调用(处理请求)"); response.getWriter().write("生命周期测试成功!"); } @Override public void destroy() { System.out.println("4. destroy() 方法被调用(销毁前)"); } @Override public ServletConfig getServletConfig() { return null; } @Override public String getServletInfo() { return null; } }

部署运行后,首次访问该 Servlet 时,控制台输出:
1. 构造方法被调用(实例化)
2. init() 方法被调用(初始化)
3. service() 方法被调用(处理请求)

再次访问时,只输出:
3. service() 方法被调用(处理请求)

关闭服务器时,输出:
4. destroy() 方法被调用(销毁前)

重要结论:
Servlet 是单例的,一个 Servlet 类在 Tomcat 中只有一个实例。
多个线程可能同时访问同一个 Servlet 的 service() 方法,因此 Servlet 是线程不安全的。


四、HttpServlet

4.1 为什么需要 HttpServlet

我们已经看到,实现 Servlet 接口需要实现 5 个方法,其中 service() 方法的参数是 ServletRequest 和 ServletResponse,没有提供专门针对 HTTP 协议的方法(如 doGet、doPost)。

而现在的 Web 应用都是基于 HTTP 协议的,所以需要针对 HTTP 协议做一层封装。

继承关系如下:
Servlet(接口)

GenericServlet(抽象类)—— 实现了 Servlet 接口,提供了通用的 servlet 功能

HttpServlet(抽象类)—— 继承自 GenericServlet,专门处理 HTTP 请求

HttpServlet 为我们提供了以下便利:
自动将 ServletRequest 和 ServletResponse 转换为 HttpServletRequest 和 HttpServletResponse。
提供了 doGet()、doPost()、doPut()、doDelete() 等方法,分别对应不同的 HTTP 请求方法。
开发者只需继承 HttpServlet,重写需要处理的方法即可,无需再实现 service()。

4.2 HTTP 请求方法概述

HTTP 协议定义了多种请求方法,常用的有以下几种:

GET:请求获取指定资源,常用于查询操作(如搜索、查看详情)。参数在 URL 中可见,有长度限制。

POST:向服务器提交数据,常用于表单提交、上传文件等。参数在请求体中,安全性相对较好。

PUT:更新指定资源,用于修改数据。

DELETE:删除指定资源。

HEAD:类似 GET,但只返回响应头,不返回响应体,用于检查资源是否存在。

OPTIONS:查询服务器支持的请求方法。

TRACE:用于诊断,回显服务器收到的请求。

CONNECT:用于代理传输,如 HTTPS。

在实际开发中,90% 以上的场景只需要 GET 和 POST 两种方法。

4.3 创建 HttpServlet 的两种方式

方式一:手动创建类继承 HttpServlet

package cn.tx.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class Servlet2 extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.getWriter().write("处理 GET 请求"); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // POST 请求默认调用 doGet,也可以单独实现 doGet(request, response); } }

然后在 web.xml 中配置:

<servlet> <servlet-name>hello2</servlet-name> <servlet-class>cn.tx.servlet.Servlet2</servlet-class> </servlet> <servlet-mapping> <servlet-name>hello2</servlet-name> <url-pattern>/hello2</url-pattern> </servlet-mapping>

方式二:使用 IDEA 快捷创建

在 IDEA 中,右键项目 → New → Servlet,填写类名和包名,IDEA 会自动生成继承 HttpServlet 的代码框架,并在 web.xml 中自动注册。开发者只需在 doGet/doPost 中编写业务逻辑即可。

4.4 load-on-startup 配置(创建顺序)

默认情况下,Servlet 在第一次被访问时才创建实例。但有些 Servlet 需要在服务器启动时就完成初始化(例如数据库连接池的初始化、定时任务调度等),可以通过 <load-on-startup> 实现。

配置方式:

<servlet> <servlet-name>hello</servlet-name> <servlet-class>cn.tx.servlet.Servlet1</servlet-class> <load-on-startup>1</load-on-startup> </servlet>

</servlet>

<load-on-startup> 的值是一个正整数,表示加载顺序:
值越小,优先级越高,越先被加载。
如果有多个 Servlet 都配置了该元素,Tomcat 会按照值从小到大依次创建。
值从 0 开始,通常使用 1、2、3...。

配置了 load-on-startup 的 Servlet,在 Tomcat 启动时就会创建实例并调用 init() 方法,而不需要等待第一次请求。


五、ServletConfig

5.1 ServletConfig 概述

ServletConfig 对象对应 web.xml 中某个 <servlet> 元素的配置信息。每个 Servlet 都有自己的 ServletConfig 对象。

ServletConfig 的主要作用:
获取当前 Servlet 在 web.xml 中配置的名称(<servlet-name>)。
获取当前 Servlet 的初始化参数(<init-param>)。
获取 ServletContext 对象。

ServletConfig 对象由 Tomcat 创建,通过 init() 方法传递给 Servlet。我们需要在 init() 方法中保存它,以便后续使用。

5.2 配置初始化参数

在 web.xml 的 <servlet> 元素中,可以添加多个 <init-param> 子元素来配置初始化参数:

<servlet> <servlet-name>hello</servlet-name> <servlet-class>cn.tx.servlet.Servlet1</servlet-class> <!-- 初始化参数 --> <init-param> <param-name>username</param-name> <param-value>root</param-value> </init-param> <init-param> <param-name>password</param-name> <param-value>txjava</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>

注意:<init-param> 必须写在 <servlet> 元素内部,不能写在 <servlet-mapping> 中。

5.3 获取初始化参数代码示例

在 Servlet 的 init() 方法中,通过 ServletConfig 对象获取参数:

@Override public void init(ServletConfig config) throws ServletException { // 获取单个参数 String username = config.getInitParameter("username"); System.out.println("username: " + username); // 获取所有参数名称(Enumeration 枚举类型) Enumeration<String> parameterNames = config.getInitParameterNames(); while (parameterNames.hasMoreElements()) { String paramName = parameterNames.nextElement(); String paramValue = config.getInitParameter(paramName); System.out.println(paramName + ": " + paramValue); } }

在 HttpServlet 的子类中,可以直接调用 getServletConfig() 方法获取 ServletConfig 对象,无需在 init() 中保存。


六、Servlet 路径映射

6.1 URL-Pattern 三种匹配方式

<url-pattern> 的配置有三种方式,每种方式的语法和适用场景不同:

方式一:完全路径匹配
以 "/" 开头,表示一个精确的路径。
示例:/hello、/user/login

、/admin/user/list
特点:只能匹配一个固定的路径,精确度最高。

方式二:目录匹配
以 "/" 开头,以 "/*" 结尾,表示匹配该目录下的所有路径。
示例:/admin/*、/user/*、/*
特点:匹配指定目录下的所有请求,适用于批量路由。

方式三:扩展名匹配
不以 "/" 开头,以 ".*" 开头,表示匹配具有特定扩展名的请求。
示例:*.do、*.action、*.jsp
特点:匹配所有以指定扩展名结尾的请求。

6.2 匹配优先级规则

当一个请求 URL 同时匹配多个 <url-pattern> 时,Tomcat 按照以下优先级选择:

优先级从高到低:
第 1 优先级:完全路径匹配(精确匹配)
第 2 优先级:目录匹配(越长越精确越优先)
第 3 优先级:扩展名匹配

6.3 常见错误与经典案例

错误示例:
<url-pattern>/*.do</url-pattern> ← 这是错误的!目录匹配和扩展名匹配不能混用。

经典案例解析:

假设有以下 4 个 Servlet 映射:
Servlet11 → /abc/*
Servlet22 → /*
Servlet33 → /abc
Servlet44 → *.do

问题 1:访问 /abc/a.html
匹配结果:Servlet11(/abc/*)和 Servlet22(/*)都匹配,但 /abc/* 更精确,所以 Servlet11 响应。

问题 2:访问 /abc
匹配结果:Servlet11(/abc/*)和 Servlet33(/abc)都匹配,但完全路径匹配优先级最高,所以 Servlet33 响应。

问题 3:访问 /abc/a.do
匹配结果:Servlet11(/abc/*)和 Servlet44(*.do)都匹配,但目录匹配优先级高于扩展名匹配,所以 Servlet11 响应。

问题 4:访问 /a.do
匹配结果:Servlet22(/*)和 Servlet44(*.do)都匹配,目录匹配优先级高于扩展名匹配,所以 Servlet22 响应。

问题 5:访问 /xxx/yyy/a.do
匹配结果:Servlet22(/*)和 Servlet44(*.do)都匹配,目录匹配优先级更高,所以 Servlet22 响应。

总结:精确匹配 > 长目录匹配 > 短目录匹配 > 扩展名匹配。


七、相对路径与绝对路径

7.1 相对路径

相对路径是相对于当前资源所在的路径,通过 "."(当前目录)和 ".."(上级目录)来定位目标资源。

示例:
当前页面路径:http://localhost:8080/day5/user/list.html
目标资源:../index.html → 实际指向 http://localhost:8080/day5/index.html

相对路径的缺点:根据当前路径不同,写法也会不同,容易出错,不推荐在 Web 开发中使用。

7.2 绝对路径

绝对路径是指从根目录开始的完整路径。

方式一:带协议的完整路径(跨网站)
http://localhost:8080/day5/hello

方式二:以 "/" 开头的路径(同一站点内)
/day5/hello

7.3 客户端路径与服务器端路径的区别

在 Web 开发中," / " 在不同场景下有不同含义:

客户端路径(浏览器地址栏、HTML 中的 href、src 等):
"/" 代表服务器的根路径,即 http://localhost:8080/。
示例:<a href="/day5/hello"> 实际访问 http://localhost:8080/day5/hello

服务器端路径(Servlet 跳转、请求转发、包含等):
"/" 代表 Web 应用的根路径,即 http://localhost:8080/项目名/。
示例:request.getRequestDispatcher("/hello") 转发到当前项目下的 /hello

重要结论:
客户端使用时,绝对路径需要带上项目上下文名称。
服务器端使用时,绝对路径不需要带项目上下文名称,Tomcat 会自动补全。


八、ServletContext

8.1 ServletContext 概述

ServletContext 是一个全局的、所有用户共享的存储空间。它的生命周期与 Web 应用一致:当 Tomcat 启动 Web 应用时创建,当 Web 应用被卸载或 Tomcat 关闭时销毁。

重要特性:
一个 Web 应用只有一个 ServletContext 对象。
所有 Servlet 共享同一个 ServletContext 对象。
可以用来实现 Servlet 之间的数据通信。
被称为 "context 域对象",可以存储键值对数据。

8.2 ServletContext API 详解

主要方法:
Object getAttribute(String name)
功能:获取绑定在 context 中的属性值。

void setAttribute(String name, Object object)
功能:向 context 中绑定属性(键值对)。

String getContextPath()
功能:获取当前 Web 应用的上下文路径(即项目名)。

String getInitParameter(String name)
功能:获取 Web 应用的全局初始化参数。

String getRealPath(String path)
功能:将虚拟路径转换为文件系统中的真实物理路径。

InputStream getResourceAsStream(String path)
功能:获取指定路径资源的输入流。

8.3 获取全局初始化参数

在 web.xml 中配置全局参数(<context-param> 是 <web-app> 的直接子元素):

<context-param> <param-name>company</param-name> <param-value>达内教育</param-value> </context-param>

在 Servlet 中获取:

ServletContext servletContext = getServletContext(); String company = servletContext.getInitParameter("company"); System.out.println("company: " + company);

System.out.println("company: " + company);

注意:<context-param> 与 <init-param> 的区别:
<context-param> 是全局的,所有 Servlet 都能获取到。
<init-param> 是单个 Servlet 私有的,只能被当前 Servlet 获取。

8.4 实现数据共享:统计网站访问次数

使用 ServletContext 实现所有用户共享的访问计数器:

@Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取 ServletContext 对象 ServletContext servletContext = getServletContext(); // 从 context 中获取访问次数 Integer visitNums = (Integer) servletContext.getAttribute("visitNums"); // 如果为空,说明是第一次访问,初始化为 0 visitNums = (visitNums == null) ? 0 : visitNums; // 次数加 1 并保存回 context visitNums++; servletContext.setAttribute("visitNums", visitNums); // 响应给客户端 response.setCharacterEncoding("UTF-8"); response.getWriter().write("当前网站访问人次: " + visitNums); }

每次用户访问该 Servlet,计数器都会加 1,且所有用户看到的是同一个计数结果。

8.5 读取资源文件的三种方式

在 JavaWeb 项目中,经常需要读取配置文件(如 .properties 文件),以下是三种常用的读取方式:

方式一:使用 ServletContext.getRealPath() 获取绝对路径后再读取

ServletContext servletContext = getServletContext(); // 参数是相对于 Web 应用根目录的路径 String path = servletContext.getRealPath("tx.properties"); System.out.println("绝对路径: " + path); try (InputStream is = new FileInputStream(path)) { Properties p = new Properties(); p.load(is); String username = p.getProperty("username"); System.out.println("username: " + username); } catch (Exception e) { e.printStackTrace(); }

方式二:使用 ServletContext.getResourceAsStream() 直接获取流

ServletContext servletContext = getServletContext(); // 第一个 "/" 代表 Web 应用的根目录 try (InputStream is = servletContext.getResourceAsStream("/WEB-INF/tx1.properties")) { Properties p = new Properties(); p.load(is); String username = p.getProperty("username"); System.out.println("username: " + username); } catch (Exception e) { e.printStackTrace(); }

方式三:使用类加载器读取 classpath 下的资源

// 使用当前类的类加载器,读取 classes 目录下的资源 try (InputStream is = this.getClass().getClassLoader() .getResourceAsStream("tx2.properties")) { Properties p = new Properties(); p.load(is); String username = p.getProperty("username"); System.out.println("username: " + username); } catch (Exception e) { e.printStackTrace(); }

三种方式对比:
方式一:需要先获取绝对路径,再创建流,步骤稍多。
方式二:直接获取流,更简洁,推荐用于 Web 项目中读取资源。
方式三:不依赖 ServletContext,通用性最强,任何 Java 类都可以使用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询