一、Servlet是什么?
Servlet 是 Java 提供的一门动态 Web 资源开发技术,也是 JavaEE 规范中的重要组成部分。其本质是一个接口,开发者需要自定义类实现该接口,并由 Web 服务器(如 Apache Tomcat)负责运行和管理。
核心职责:
接收浏览器发送的 HTTP 请求 → 处理业务逻辑 → 将动态生成的响应结果返回给浏览器,从而在 B/S 架构下实现服务端逻辑的开发。
二、Servlet执行流程
当浏览器发起一个 HTTP 请求(例如 http://localhost:8080/web-demo/demo1)时,完整的执行流程如下:
- 浏览器将 HTTP 请求发送至 Web 服务器(Tomcat)。
- Tomcat 解析请求 URL,定位到对应的 Web 项目(
web-demo)。 - 在项目中找到实现了 Servlet 接口的自定义类(例如
ServletDemo1)。 - Tomcat 自动创建该 Servlet 的实例,并调用其
service()方法来处理请求。 service()方法执行完毕后,Tomcat 将生成的响应结果返回给浏览器。- 浏览器解析响应数据并渲染页面。
关键问题解答
- Servlet 由谁创建?方法由谁调用?
Servlet 对象由 Web 服务器(Tomcat)负责创建,其所有生命周期方法(如service())也均由服务器调用。开发者只需专注于接口逻辑的实现。 - 服务器如何确保一定有
service()方法?
因为自定义 Servlet 类必须实现 Servlet 接口,而该接口强制定义了service()方法,因此服务器可以安全地对该方法进行调用。
三、Servlet生命周期
Servlet 运行于 Servlet 容器(即 Web 服务器)中,其生命周期完全由容器管理。整个过程可划分为 4 个核心阶段:
| 阶段 | 触发时机 | 执行方法 | 执行次数 | 核心作用 |
|---|---|---|---|---|
| 加载和实例化 | 默认首次访问时(可通过配置修改为启动时) | - | 1次 | 创建 Servlet 对象 |
| 初始化 | 实例化完成后立即执行 | init() |
1次 | 加载配置、建立连接等初始化工作 |
| 请求处理 | 每次接收到访问请求时 | service() |
N次 | 处理具体的 HTTP 请求并生成响应 |
| 服务终止 | 服务器关闭或进行内存释放时 | destroy() |
1次 | 释放资源,对象等待被垃圾回收 |
loadOnStartup 配置说明
可以通过 @WebServlet(urlPatterns = "/demo", loadOnStartup = 1) 注解中的 loadOnStartup 属性来控制 Servlet 的创建时机:
- 负整数(默认值) :懒加载模式,仅在 Servlet 第一次被访问时才创建对象。
- 0 或正整数 :饿汉式加载,Tomcat 启动时 即创建 Servlet 对象。数字越小,启动时被初始化的优先级越高。
深度解析:loadOnStartup 的优先级到底指什么?
结论先行:该优先级仅代表服务器启动时 Servlet 的实例化与初始化顺序,与 URL 访问匹配优先级完全无关。
1. 核心规则
- 负数 / 不配置:默认懒加载,首次访问时才创建。
- 0 或正整数 :Tomcat 启动时立即创建。数字越小,越早被实例化和执行
init()方法。
2. 代码示例说明
@WebServlet(urlPatterns = "/a", loadOnStartup = 1) // 第二个创建
public class ServletA{}
@WebServlet(urlPatterns = "/b", loadOnStartup = 2) // 第三个创建
public class ServletB{}
@WebServlet(urlPatterns = "/c", loadOnStartup = 0) // 第一个创建
public class ServletC{}
Tomcat 启动时的创建顺序为:ServletC (0) → ServletA (1) → ServletB (2)。
3. 应用场景
- 需要在项目启动时预加载配置、建立数据库连接池或初始化缓存的 Servlet,应将
loadOnStartup设置为较小的正整数(如 1)。 - SpringMVC 的
DispatcherServlet必须配置此项,以确保其在启动时就绪。
四、Servlet核心方法详解
Servlet 接口定义了 5 个核心方法,职责分明:
void init(ServletConfig config)
初始化方法。在 Servlet 对象创建后由容器调用,仅执行 1 次 。参数ServletConfig可用于获取 Servlet 的初始化配置参数。void service(ServletRequest req, ServletResponse res)
核心服务方法。每次请求访问该 Servlet 时都会被调用 。业务逻辑的核心代码通常在此方法中编写。ServletRequest封装了请求数据,ServletResponse用于构建响应信息。void destroy()
销毁方法。当 Servlet 被容器决定移除时(如服务器关闭)调用,仅执行 1 次。用于释放文件句柄、数据库连接等系统资源。ServletConfig getServletConfig()
用于返回当前 Servlet 的ServletConfig对象。String getServletInfo()
用于返回 Servlet 的描述性信息,如作者、版本等。
五、Servlet体系结构与HttpServlet原理
直接实现 Servlet 接口需要实现所有 5 个方法,开发效率不高。因此,Java Servlet API 提供了更为便捷的抽象继承体系:
Servlet接口:根基接口,定义核心规范。GenericServlet抽象类 :对 Servlet 接口的通用实现,提供了除service()外所有方法的默认实现,并将service()保留为抽象方法。HttpServlet抽象类 :继承自GenericServlet,专门针对 HTTP 协议 进行了封装。
HttpServlet 使用步骤
在实际 B/S 项目开发中,我们通常通过继承 HttpServlet 类来创建自定义 Servlet:
- 定义一个类,继承
javax.servlet.http.HttpServlet。 - 根据需要重写
doGet()和doPost()方法。
HttpServlet 内部原理
HttpServlet 重写了 service() 方法。其内部逻辑会自动解析请求的 HTTP 方法(GET、POST 等),并根据方法类型分发 到对应的 doXxx() 方法中:
- GET 请求 → 调用
doGet() - POST 请求 → 调用
doPost()
这使开发者无需在 service() 方法中手动编写冗长的 if-else 判断逻辑。
直观对比:HttpServlet 到底简化了什么?
如果直接实现 Servlet 接口,我们需要在 service() 方法中手动判断请求类型,写出如下臃肿的代码:
public class OldWayServlet implements Servlet {
// 必须实现 init, destroy, getServletConfig, getServletInfo 等方法 ...
@Override
public void service(ServletRequest req, ServletResponse res) {
// 1. 强制类型转换,因为 ServletRequest 没有获取 HTTP 方法的 API
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 2. 获取请求方式
String method = request.getMethod();
// 3. 臃肿的 if-else 判断
if ("GET".equals(method)) {
// 处理 GET 请求的逻辑
doGetLogic(request, response);
} else if ("POST".equals(method)) {
// 处理 POST 请求的逻辑
doPostLogic(request, response);
} else if ("PUT".equals(method)) {
// 处理 PUT 请求 ...
}
// ... 还要处理 HEAD, DELETE, OPTIONS 等
}
private void doGetLogic(HttpServletRequest req, HttpServletResponse resp) { /* ... */ }
private void doPostLogic(HttpServletRequest req, HttpServletResponse resp) { /* ... */ }
}
而使用 HttpServlet 之后,代码变得极其简洁:
public class ModernServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
// 专注处理 GET 请求,无需任何类型转换和 if-else 判断
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
// 专注处理 POST 请求
doGet(req, resp); // 可以方便地复用逻辑
}
}
💡 一句话总结: HttpServlet 实际就是简化了原始 Servlet 接口中判断请求方法的臃肿代码,让开发者可以更专注地编写针对不同 HTTP 方法的业务逻辑。
六、Servlet urlPattern 配置规则
在 @WebServlet 注解中,urlPatterns(或 value)属性用于配置 Servlet 的访问路径。它支持以下 4 种匹配模式,且遵循明确的优先级顺序:
| 匹配模式 | 配置示例 | 访问示例 | 优先级 | 说明 |
|---|---|---|---|---|
| 1. 精确匹配 | /user/select |
.../user/select |
最高 | 路径必须完全一致。 |
| 2. 目录匹配 | /user/* |
.../user/aaa .../user/bbb |
第二 | 匹配 /user/ 开头的所有路径。 |
| 3. 扩展名匹配 | *.do |
.../aaa.do .../bbb.do |
第三 | 匹配以 .do 结尾的所有路径。注意:不能以 / 开头。 |
| 4. 任意匹配 | / 或 /* |
所有未匹配路径 | 最低 | 详见下方深度解析。 |
/ 与 /* 的核心区别(表格速览)
| 路径 | 行为描述 | 应用场景 |
|---|---|---|
/ |
覆盖 Tomcat 内置的 DefaultServlet。当所有其他 url-pattern 都无法匹配时,请求会交由该 Servlet 处理。 |
SpringMVC 的前端控制器 DispatcherServlet 常配置为此路径。 |
/* |
拦截所有请求 ,优先级高于 /。包括 .jsp、.html、.css 等静态资源请求都会被拦截。 |
通常用于全局过滤器(Filter),极少用于 Servlet 映射。 |
深度解析:/ 与 /* 对静态资源影响的终极区别
1. 为什么配置 / 会导致静态资源无法访问?
这是 90% 的新手都会遇到的经典问题。其根本原因在于 Tomcat 内置了一个名为 DefaultServlet 的 Servlet,专门负责处理静态资源。
-
Tomcat 的默认配置
在 Tomcat 的全局配置文件conf/web.xml中有如下配置:<servlet> <servlet-name>default</servlet-name> <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>DefaultServlet的映射路径正是/。它的作用就是读取服务器磁盘上的.html、.css、.js、图片等文件并返回给客户端。 -
覆盖机制
当你在项目中定义了@WebServlet("/")时,应用级别的配置会覆盖 Tomcat 的全局默认配置 。此时,所有本应交给DefaultServlet处理的静态资源请求,都进入了你的自定义 Servlet。 -
后果
你的自定义 Servlet 只处理业务逻辑,并不会去读取和返回静态文件。因此,浏览器请求静态资源时会收到 404 错误或进入了错误的业务逻辑。 -
通俗举例:为什么原来的
http://.../aaa/a.html突然不能用了?- 原来(无覆盖时) :访问
/aaa/a.html→ 无精确匹配的 Servlet → 路由给 Tomcat 的DefaultServlet→ 找到文件并返回 ✅ - 覆盖后(写了
@WebServlet("/")) :访问/aaa/a.html→ 无精确匹配的 Servlet → 路由给你的 Servlet → 你的 Servlet 无法处理静态文件 → 报错或404 ❌
- 原来(无覆盖时) :访问
一句话总结:
/路径只能由一个 Servlet 接管。你接盘后,原来的"静态资源管理员"就下岗了。
2. /* 模式的真正威力
/*是一个真正意义上的全局通配符。- 它的优先级极高,会无条件拦截所有请求 ,包括
.jsp、.html、.png、.js等。 - 后果 :即使没有覆盖
DefaultServlet,所有请求也会先被/*拦截。若处理不当,静态资源将彻底失效,且恢复起来更为复杂。
3. 匹配优先级总结
精确路径 > 目录路径 > 扩展名路径 >
/*>/
七、Servlet 的两种配置方式
1. 注解配置(推荐,Servlet 3.0+)
无需编写 web.xml 文件,直接在类上使用 @WebServlet 注解即可完成配置。
@WebServlet(urlPatterns = "/demo", loadOnStartup = 1)
public class ServletDemo extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 处理 GET 请求逻辑
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 处理 POST 请求逻辑,可复用 doGet
doGet(req, resp);
}
}
2. XML 配置(传统方式,Servlet 3.0 之前)
在项目的 WEB-INF/web.xml 文件中进行集中配置。
步骤:
-
编写 Servlet 类。
-
在
<web-app> <servlet> <servlet-name>demo5</servlet-name> <servlet-class>com.itheima.web.servlet.ServletDemo5</servlet-class> <load-on-startup>1</load-on-startup> </servlet>web.xml中注册 Servlet 并配置映射路径。</web-app><!-- 2. 配置 Servlet 映射 --> <servlet-mapping> <servlet-name>demo5</servlet-name> <url-pattern>/demo5</url-pattern> </servlet-mapping>
web.xml 核心说明:
- 该文件必须放置在
WEB-INF/目录下,是 Tomcat 的强制约定。 - Tomcat 启动时会自动扫描并加载此文件中的配置。
八、Servlet 与 SpringMVC 的关系
许多初学者容易混淆 @Controller 注解的类与 Servlet 的关系,这里给出明确界定:
- Tomcat 只认 Servlet 接口。 我们编写的带
@Controller的类本身并不是 Servlet。 - SpringMVC 中,真正实现 Servlet 接口的是
DispatcherServlet(前端控制器)。 DispatcherServlet是 Tomcat 中唯一的中央调度 Servlet 。所有进入 SpringMVC 应用的请求,首先会被DispatcherServlet拦截,再由它根据@RequestMapping等规则分发给具体的@Controller处理。
核心协作流程
- Tomcat 启动,加载
DispatcherServlet。 - 浏览器发起请求 → Tomcat 将请求交给
DispatcherServlet。 DispatcherServlet解析 URL,调用对应的@Controller方法。@Controller返回处理结果 →DispatcherServlet将结果响应给浏览器。
九、总结
Servlet 是 Java Web 开发的基石,也是 SpringMVC、Struts 等上层框架的底层核心。深入理解 Servlet 的工作原理、生命周期及配置细节,可以帮助我们:
- 透彻理解 Web 容器处理请求的完整流程。
- 快速诊断和解决项目中的请求拦截、路径映射错误等问题。
- 为深入学习 SpringMVC 等框架的底层实现打下坚实基础。
附录:经典面试题速览
- Servlet 生命周期是什么?
答:实例化 → 初始化(init())→ 服务(service())→ 销毁(destroy())。 /和/*的区别是什么?
答:/会覆盖 Tomcat 默认的静态资源 Servlet,拦截其他规则未匹配的请求;/*优先级更高,会拦截所有请求,包括静态资源。- HttpServlet 中
service()方法的作用是什么?
答:自动解析请求的 HTTP 方法,并将其分发到对应的doGet()、doPost()等处理方法中。 - Servlet 是单例还是多例?
答:Servlet 是单例的,服务器全局仅创建一个实例。因此,应避免在 Servlet 中定义线程不安全的成员变量。 loadOnStartup的作用和优先级指什么?
答:配置 Servlet 对象的创建时机。负数代表懒加载,0/正整数代表启动时加载,数字越小启动时初始化的顺序越靠前。它与 URL 的访问优先级无关。- 为什么配置
@WebServlet("/")后静态资源无法访问?
答:因为覆盖了 Tomcat 自带的DefaultServlet,导致静态资源请求没有合适的处理器进行响应。