Servlet 有哪三大组件?Listener 监听器有什么用?三大作用域怎么选?
简化版
Servlet 规范的三大组件是:Servlet(处理请求、生成响应,核心)、Filter(过滤器,在请求到达 Servlet 前后做拦截处理,如编码、鉴权、日志)、Listener(监听器,监听 Web 应用中各种「事件」并做出响应)。三者分工:Servlet 干活、Filter 拦截、Listener 监听。Listener 常被忽略但很实用——它能监听「应用启动/关闭」(ServletContextListener,做全局初始化/资源清理)、「Session 创建/销毁」(HttpSessionListener,做在线人数统计)、「属性变化」等。此外 Servlet 有三大作用域对象:ServletContext(application,整个应用共享,生命周期最长)、HttpSession(session,一个用户会话内共享)、HttpServletRequest(request,一次请求内共享,生命周期最短),按「数据要共享多久/多大范围」选择。
详细版
Servlet 三大组件:
| 组件 | 作用 | 典型用途 |
|---|---|---|
| Servlet | 处理请求、生成响应 | 业务逻辑入口(现在多被 Spring MVC 的 DispatcherServlet 统一) |
| Filter | 请求到达 Servlet 前后拦截 | 编码、鉴权、跨域、日志、压缩 |
| Listener | 监听 Web 应用的事件 | 应用初始化、在线人数统计、资源清理 |
Listener 的三类常见监听器(按监听的对象分):
① 监听 ServletContext(应用级):
ServletContextListener:应用启动/关闭时触发(contextInitialized/contextDestroyed)
→ 做全局初始化(加载配置、建连接池)、关闭时清理资源
ServletContextAttributeListener:application 作用域属性增删改时触发
② 监听 HttpSession(会话级):
HttpSessionListener:Session 创建/销毁时触发(sessionCreated/sessionDestroyed)
→ 统计在线人数(创建+1、销毁-1)
HttpSessionAttributeListener:session 属性变化时触发
③ 监听 ServletRequest(请求级):
ServletRequestListener:请求初始化/销毁时触发
ServletRequestAttributeListener:request 属性变化时触发
三大作用域对象(按数据共享范围/生命周期):
| 作用域 | 对象 | 范围 | 生命周期 |
|---|---|---|---|
| application | ServletContext | 整个 Web 应用(所有用户共享) | 应用启动到关闭(最长) |
| session | HttpSession | 一个用户的一次会话 | 会话创建到超时/失效 |
| request | HttpServletRequest | 一次请求(含 forward 转发) | 一次请求处理期间(最短) |
// 各作用域存取数据
request.setAttribute("key", value); // request 域:这次请求内共享
session.setAttribute("key", value); // session 域:这个用户会话内共享
getServletContext().setAttribute("key", value); // application 域:全应用共享
// Listener 示例:统计在线人数
public class OnlineCountListener implements HttpSessionListener {
public void sessionCreated(HttpSessionEvent e) { count++; } // 有人上线
public void sessionDestroyed(HttpSessionEvent e) { count--; } // 有人下线
}
⚠️ 用作用域共享数据时要选对范围——范围越大、生命周期越长的作用域,越要小心内存和并发问题。
ServletContext(application)是全应用共享的,往里放大量数据会一直占内存不释放;且它被所有请求线程并发访问,存可变数据要注意线程安全。所以「能用小作用域就别用大的」——请求内的数据用 request、会话内的用 session、真正全局共享的才用 application。
完整版教学
一、Servlet 三大组件的分工
Servlet 规范定义了 Web 应用的三个核心组件,各司其职:
Servlet:处理请求的"主力"——接收 HttpServletRequest、处理业务、写 HttpServletResponse
(现在实际开发中,业务 Servlet 大多被 Spring MVC 的 DispatcherServlet 统一接管)
Filter:请求的"关卡"——在请求到达 Servlet 之前、响应返回之后做拦截处理
多个 Filter 组成链,可做编码、鉴权、日志、压缩等横切处理
Listener:应用的"监听者"——监听 Web 应用里发生的各种事件,事件发生时被回调
不直接处理请求,而是在"应用启动、会话创建、属性变化"等时机做响应
一句话分工:Servlet 干活(处理请求)、Filter 拦截(请求前后加工)、Listener 监听(对事件做反应)。前两个(Servlet、Filter)大家熟,Listener 常被忽略——它是「事件驱动」的组件,不参与请求处理的主流程,而是在关键时机(应用启停、会话生死)被容器回调。理解「三大组件 = 处理 + 拦截 + 监听」,就有了 Servlet 组件体系的整体图景。
二、Listener 是什么:监听 Web 事件
Listener(监听器)是观察者模式的应用——它「订阅」Web 应用里的某类事件,事件发生时容器自动回调它:
Web 应用里有很多"事件":应用启动了、有新用户会话了、某个属性变了...
Listener 就是"订阅这些事件、事件发生时被通知"的组件
工作方式(观察者模式):
你实现一个 Listener 接口(如 ServletContextListener)
→ 注册到容器(web.xml 或 @WebListener 注解)
→ 对应事件发生时(如应用启动),容器自动调用你的方法
Listener 的价值是「在恰当的时机做恰当的事」——它让你能挂钩到 Web 应用的生命周期和状态变化上。比如「应用一启动就加载配置/建连接池」「一有新会话就统计在线人数」「应用关闭时清理资源」,这些都是「监听事件 + 做出响应」的场景。理解「Listener = 订阅 Web 事件的监听器」,就理解了它和 Servlet/Filter 的本质区别——它不处理请求,而是响应事件。
三、按监听对象分类的三类监听器
Listener 按「监听什么对象」分成三类,对应三大作用域:
① 监听 ServletContext(应用级,最常用):
ServletContextListener — 监听应用启动/关闭
contextInitialized():应用启动时调用 → 做全局初始化(Spring 的 ContextLoaderListener 就是它!加载 Spring 容器)
contextDestroyed():应用关闭时调用 → 清理资源
ServletContextAttributeListener — 监听 application 域属性的增删改
② 监听 HttpSession(会话级):
HttpSessionListener — 监听会话创建/销毁
sessionCreated():新会话 → 在线人数 +1
sessionDestroyed():会话销毁 → 在线人数 -1
HttpSessionAttributeListener — 监听 session 域属性变化
(还有 HttpSessionBindingListener/HttpSessionActivationListener 用于对象绑定/钝化)
③ 监听 ServletRequest(请求级):
ServletRequestListener — 监听请求初始化/销毁
ServletRequestAttributeListener — 监听 request 域属性变化
最常用、最重要的是 ServletContextListener——它监听「应用启动/关闭」,是做「全局一次性初始化」的标准位置。一个经典例子:Spring 的 ContextLoaderListener 就是一个 ServletContextListener——Web 应用启动时它的 contextInitialized 被调用,从而启动 Spring 容器(加载 applicationContext.xml/配置类)。所以「Spring 是怎么随 Web 应用启动的」答案就在这——靠 Listener 挂钩应用启动事件。理解三类监听器对应三大作用域,就能针对不同需求选对监听器。
四、三大作用域:数据共享的范围与生命周期
Servlet 有三大作用域对象,用于在不同范围内共享数据,核心区别是「共享范围」和「生命周期」:
作用域从大到小(生命周期从长到短):
ServletContext(application):整个 Web 应用共享
→ 所有用户、所有请求都能访问;应用启动到关闭一直存在(最长)
HttpSession(session):一个用户的会话内共享
→ 同一用户的多次请求能访问;会话创建到超时/失效
HttpServletRequest(request):一次请求内共享
→ 只有这次请求(含 forward 转发)能访问;请求处理完就没了(最短)
选择依据是「这个数据要给谁看、活多久」:
只在这次请求内传递(如 Controller 传给视图的数据)→ request 域
需要在用户多次请求间保持(如登录状态、购物车)→ session 域
全应用共享、所有用户通用(如全局配置、字典数据、在线人数)→ application 域
一个关键原则(再强调):能用小作用域就别用大的。作用域越大,生命周期越长、共享范围越广——application 域的数据一直占内存不释放、被所有请求并发访问(有线程安全问题)。所以「数据该活多久就放多久的作用域」——请求内的用 request、会话内的用 session、真全局的才用 application。滥用大作用域会导致内存占用高、并发问题、数据串味。
五、组件的注册方式:web.xml 与注解
三大组件都要「注册」到容器才生效,有两种方式:
① 传统 web.xml 配置(老方式):
<servlet>/<servlet-mapping> 注册 Servlet
<filter>/<filter-mapping> 注册 Filter
<listener> 注册 Listener
② Servlet 3.0+ 注解(现代方式,免 web.xml):
@WebServlet("/path") 注册 Servlet
@WebFilter("/path") 注册 Filter
@WebListener 注册 Listener
现代开发(尤其 Spring Boot)几乎不写 web.xml——Servlet 3.0 引入了注解和 ServletContainerInitializer(SPI 机制),让组件能通过注解或编程式注册,无需 XML 配置。Spring Boot 更进一步:内嵌 Tomcat,用 @Bean(FilterRegistrationBean、ServletRegistrationBean、ServletListenerRegistrationBean)注册这些组件,或直接用 Spring 的 @Component + 相应机制。所以「为什么现在不写 web.xml 了」——Servlet 3.0 的注解 + SPI + Spring Boot 的自动注册取代了它。理解注册方式的演进,就理解了从「XML 配置」到「注解/编程式」的现代转变。
六、三大组件与 Spring 的关系
现代 Spring/Spring Boot 应用里,这三大组件大多被 Spring 「接管」或封装,理清关系避免混淆:
| Servlet 组件 | 在 Spring 里的对应/关系 |
|---|---|
| Servlet | 业务 Servlet 被 DispatcherServlet(一个 Servlet)统一,请求由它分发给 Controller |
| Filter | Spring Security 的过滤器链就是一堆 Filter;也可自定义 Filter |
| Listener | Spring 的 ContextLoaderListener 是 Listener;Spring 事件机制是应用内的更高层封装 |
| 作用域 | Spring 有 @RequestScope/@SessionScope/@ApplicationScope 对应三大作用域的 Bean |
关键认知:Spring 不是抛弃了 Servlet 组件,而是建立在它们之上并做了封装——DispatcherServlet 本身就是一个 Servlet(Spring MVC 的入口),Spring Security 用 Filter 实现,ContextLoaderListener 用 Listener 启动容器。所以「Servlet 三大组件」是底层地基,Spring 是上层建筑。理解这层关系,就不会把「Servlet 组件」和「Spring 组件」对立起来——用 Spring 时,底层仍是 Servlet 规范在支撑,只是你直接打交道的是 Spring 的抽象。这也把这道题和前面学的 Spring MVC、Spring Security 串了起来。
记忆钩子:「Servlet 三大组件:Servlet 处理请求、Filter 拦截、Listener 监听事件;Listener 三类对应三大作用域——ServletContextListener(应用启停,Spring 的 ContextLoaderListener 就是它)、HttpSessionListener(会话创建销毁,统计在线人数)、ServletRequestListener;三大作用域 application(ServletContext 全应用最长)> session(HttpSession 会话)> request(一次请求最短),能用小的别用大的」。
七、常见误区与追问
- 误区:Servlet 只有 Servlet 和 Filter 两大组件。 是三大组件——Servlet(处理请求)、Filter(拦截)、Listener(监听事件);Listener 常被忽略但很实用(应用初始化、在线统计)。
- 误区:Listener 也参与请求处理。 不——Listener 是事件驱动的,监听「应用启停、会话生死、属性变化」等事件被回调,不处理请求主流程;处理请求是 Servlet 的活。
- 误区:数据都放 application 作用域最方便。 危险——application 全应用共享、一直占内存、被所有请求并发访问(线程安全问题);应「能用小作用域就用小的」,请求内用 request、会话内用 session。
- 误区:Spring 应用不用 Servlet 组件了。 恰恰相反——DispatcherServlet 是 Servlet、Spring Security 用 Filter、ContextLoaderListener 是 Listener,Spring 建立在 Servlet 组件之上做封装。
- 追问:ServletContextListener 有什么用?举个例子。 监听应用启动/关闭做全局初始化/清理;经典例子是 Spring 的 ContextLoaderListener——Web 应用启动时它的 contextInitialized 启动 Spring 容器(这就是 Spring 随应用启动的机制)。
- 追问:怎么用 Listener 统计在线人数? 实现 HttpSessionListener,sessionCreated(新会话)时人数 +1、sessionDestroyed(会话销毁/超时)时 -1;因为一个用户会话对应一个 HttpSession。
- 追问:三大作用域怎么选? 按「数据共享范围和存活时长」——一次请求内传递用 request(最短),用户多次请求间保持(登录态/购物车)用 session,全应用所有用户共享(全局配置)用 application(最长)。
八、加强记忆
Servlet 规范的三大组件是 Servlet(处理请求生成响应)、Filter(请求前后拦截,做编码/鉴权/日志)、Listener(监听 Web 事件)——分工是「Servlet 干活、Filter 拦截、Listener 监听」。常被忽略的 Listener 是观察者模式的应用(订阅事件、事件发生时被容器回调),按监听对象分三类:ServletContextListener(监听应用启停,做全局初始化/清理,Spring 的 ContextLoaderListener 就是它——Spring 随应用启动的机制)、HttpSessionListener(监听会话创建/销毁,统计在线人数)、ServletRequestListener(监听请求)。三大作用域对象按「共享范围/生命周期」从大到小:ServletContext(application,全应用共享、最长)> HttpSession(session,用户会话内)> HttpServletRequest(request,一次请求内、最短),选择依据是「数据给谁看、活多久」,原则是能用小作用域就别用大的(大作用域占内存、有并发问题)。注册方式从 web.xml 演进到 Servlet 3.0 注解(@WebServlet/@WebFilter/@WebListener)+ SPI + Spring Boot 自动注册。这三大组件是底层地基,Spring 建立其上做封装(DispatcherServlet 是 Servlet、Security 用 Filter、ContextLoaderListener 是 Listener)。一句话「Servlet 处理/Filter 拦截/Listener 监听三大组件、Listener 三类对应三大作用域、application>session>request 能用小别用大、Spring 建立在其上」。