第 5 章过滤器和监听器 视频讲解 过滤器和监听器是两种特殊的Servlet。过滤器可以对用户的请求和响应进行过滤,常被用于权限检查和参数编码的统一设置。监听器可以用来对Web应用程序进行监听和控制,增强Web应用程序的事件处理能力。 5.1过滤器 过滤器(Filter)是在服务器上运行的,位于被请求资源与客户端之间的起过滤功能的程序。它能够对请求和响应进行检查和修改,通常在接收到请求后执行一些通用操作,如过滤敏感词、统一字符编码和实施安全控制等。其工作原理如图51所示。 图51过滤器的工作原理 如图51所示,客户端向服务器资源发送请求。请求在到达对应资源之前,先由过滤器过滤。过滤器会检查用户请求是否符合过滤规则,如果符合过滤规则,则请求依次通过过滤器链中的过滤器,最终到达请求的资源。同理,资源做出的响应也会依次通过过滤器链最终到达客户端。过滤器根据过滤规则,可以做出如下动作: (1) 将请求发送给对应的资源(如JSP、Servlet)。 (2) 用修改后的请求调用相应的资源。 (3) 将请求发送给被请求的资源,修改响应,再将响应发送到客户端。 (4) 禁止调用被请求的资源,将请求重定向到其他资源。 5.1.1过滤器编程接口 jakarta.servlet包提供了与过滤器相关的一组接口和类,分别是Filter接口、FilterChain接口、FilterConfig接口、FilterRegistration接口、GenericFilter类和HttpFilter类,如表51所示。 表51过滤器相关的接口和类 接口、类说明 public interface Filter编写过滤器要实现的接口 public abstract class GenericFilterFilter接口的实现类。该类定义了一个通用的、独立于协议的过滤器。要编写在Web上使用的HTTP过滤器,需继承HttpFilter类 public abstract class HttpFilter extends GenericFilterFilter接口的实现类,是GenericFilter的子类,用于创建适用于Web的HTTP过滤器 public interface FilterConfig供Web容器在初始化时给过滤器传递信息用 public interface FilterChain过滤器链(FilterChain)是Servlet容器提供给开发人员的一个对象。过滤器使用FilterChain对象调用链中的下一个过滤器,如果当前过滤器是链中的最后一个过滤器,则使用FilterChain对象调用链末端的资源 public interface FilterRegistration extends Registration支持过滤器进行深入配置的接口 1. Filter接口 Filter接口是编写过滤器需要实现的接口,该接口定义了init()方法,doFilter()方法和destroy()方法,具体如表52所示。 表52Filter接口中的方法 方 法 声 明说明 void init(FilterConfig filterConfig)过滤器的初始化方法,Servlet容器创建过滤器实例后将调用该方法 void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)用于实现过滤操作。当客户请求满足过滤规则时,Servlet容器将调用过滤器的doFilter()方法完成过滤。其中参数request和response分别代表Web服务器或过滤器链中的上一个过滤器传递来的请求和响应,参数chain代表当前的过滤器链对象 void destroy()用于释放被过滤器占用的资源,如关闭IO流,关闭数据库等。该方法在Servlet容器释放过滤器对象前调用 2. FilterConfig接口 FilterConfig接口用于封装过滤器的配置信息。在过滤器初始化时,Servlet容器将FilterConfig对象作为参数传递给过滤器对象的init()方法完成初始化。FilterConfig接口中的方法如表53所示。 表53FilterConfig接口中的方法 方 法 声 明说明 String getFilterName()返回过滤器的名称 ServletContext getServletContext()返回过滤器对象运行环境中的ServletContext对象 String getInitParameter(String name)返回名为name的初始化参数的值 Enumeration<String> getInitParameterNames()返回过滤器的所有初始化参数名称的枚举 3. FilterChain接口 FilterChain接口定义了一个doFilter()方法,其原型如下: void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException 这个方法用于调用过滤器链中的下一个过滤器,如果当前过滤器是链上最后一个过滤器,则将请求提交给处理程序或者将响应发送给客户端。 4. HttpFilter类 一个抽象类,它的子类可以用来创建适用于Web站点的HTTP过滤器。用于创建过滤器的子类需要覆盖doFilter()方法。其原型如下: doFilter(jakarta.servlet.http.HttpServletRequest, jakarta.servlet.http.HttpServletResponse, jakarta.servlet.FilterChain) 5.1.2过滤器生命周期 过滤器生命周期指一个过滤器对象从创建到执行过滤再到销毁的过程。过滤器生命周期可分为创建、过滤、销毁3个阶段。表52的Filter接口中的方法就是过滤器生命周期的3个方法。 1. 创建阶段 Web容器启动的时候就会创建过滤器对象,并调用init()方法,完成对象的初始化。需要注意的是,在一次完整的请求/响应过程中,过滤器对象只会被创建一次,即init()方法只会被调用一次。 2. 过滤阶段 当客户端向目标资源发出请求时,Web容器会筛选出符合映射条件的过滤器,并按照类名的先后顺序依次执行过滤器对象的doFilter()方法。在执行过滤的doFilter()方法中可以调用Servlet API实现对请求、响应的预处理。一般来讲,对请求执行过滤后,还要将其送给过滤器链上的下一个资源。因此,过滤器对象的doFilter()方法的最后一条语句一般是调用FilterChain接口的doFilter()方法。 3. 销毁阶段 服务器关闭时,Web容器调用destroy()方法销毁过滤器对象。 5.1.3设计过滤器 对于自定义过滤器,有两种实现方案。第一,创建一个类实现Filter(jakarta.servlet.Filter)接口。第二,创建一个类继承HttpFilter(jakarta.servlet.http.HttpFilter)类。对于第一种方案,可以按需重写过滤器生命周期中的3个方法,并且请求和响应的类型不局限于HTTP请求和HTTP响应。对于第二种方案,只需覆盖父类的doFilter()方法,过滤的请求和响应类型仅限于HTTP请求和HTTP响应。 【例51】设计一个简单的过滤器,在控制台输出过滤器的创建信息和请求过滤信息。创建一个名为chapter5的动态Web项目。在com.example.filter包中创建一个过滤器和一个Servlet,代码如文件51和文件52所示。 【文件51】MyFilter.java 1package com.example.filter; 2 3//import部分略 4 5@WebFilter("/*") 6public class MyFilter extends HttpFilter { 7 8public void destroy() { 9System.out.println("filter destroyed"); 10} 11 12public void doFilter(ServletRequest request, 13ServletResponse response, FilterChain chain) 14throws IOException, ServletException { 15System.out.println("this is filter"); 16//pass the request along the filter chain 17chain.doFilter(request, response); 18} 19 20public void init(FilterConfig fConfig) 21throws ServletException { 22System.out.println("filter initialization"); 23} 24} 【文件52】MyServlet.java 1package com.example.filter; 2 3//import部分此处略 4 5@WebServlet("/MyServlet") 6public class MyServlet extends HttpServlet { 7 8protected void doGet(HttpServletRequest request, 9HttpServletResponse response) 10throws ServletException, IOException { 11PrintWriter out = response.getWriter(); 12System.out.println("this is servlet"); 13out.print("servlet response"); 14} 15//此处省略了doPost()方法 16} 在文件51中配置了过滤器生命周期的3个方法。为便于观察过滤器的创建和销毁过程,分别在init()方法和destroy()方法中增加控制台输出,如第22行和第9行所示。在执行过滤的doFilter()方法中,首先向控制台输出字符串“this is filter”(第15行),再将请求传递给过滤器链的下一个资源(第17行)。本例中的下一个资源为MyServlet。MyServlet在处理请求时首先在控制台输出字符串,再用out对象生成响应。在浏览器的地址栏输入“http://localhost:8080/chapter5/MyServlet”后,向MyServlet发出请求,可以在浏览器页面看到由Servlet生成的响应servlet response ,如图52所示。 图52Servlet生成的响应 结合过滤器的工作原理可知,在请求到达目标资源MyServlet前,由MyFilter过滤器执行过滤。执行过滤后,MyFilter再将请求送给MyServlet,由MyServlet生成客户端响应。可以通过控制台输出了解过滤器从创建到执行过滤的过程。将项目部署到Tomcat服务器上。在Tomcat启动后,可以看到控制台输出“filter initialization”,如图53所示,这说明过滤器随着Tomcat启动即完成了创建和初始化。随后,通过浏览器给MyServlet发送请求,可以看到控制台的输出 为“this is filter”和“this is servlet”两条消息。说明请求首先被过滤器过滤,再由过滤器转给Servlet处理。 图53控制台输出 创建过滤器后,还需要进行配置。与Servlet的配置类似,过滤器也有两种配置方式: 部署描述符和注解。 1. 部署描述符 可以在web.xml文件中配置过滤器的基本信息。用于配置过滤器的两个标记分别为 <filter/>和<filtermapping/>。对于例51中的过滤器MyFilter可以进行如下配置。 1<filter> 2<filter-name>MyFilter</filter-name> 3<filter-class>com.example.filter.MyFilter</filter-class> 4</filter> 5<filter-mapping> 6<filter-name>MyFilter</filter-name> 7<url-pattern>/*</url-pattern> 8</filter-mapping> 其中,以下内容需要注意: (1) <filter>标记用于注册一个过滤器。 (2) <filtername>标记用于设置Filter类的名称。 (3) <filterclass>标记用于设置Filter类的全限定名。 (4) <filtermapping>标记用于设置一个过滤器所过滤的资源。 (5) <urlpattern>标记用于匹配用户请求的URL,“/*”表示当前项目下的所有资源。即给当前项目根目录下的任何资源发出的请求都会被过滤器过滤。本例中的“/*”也可以替换为Servlet的urlpattern,即“/MyServlet”。 2. 注解 与配置Servlet类似,在创建过滤器后,可以使用标注在过滤器类上的@WebFilter注解来告知Servlet容器有哪些过滤器需要被实例化。例51中,“@WebFilter("/*")”用于匹配用户请求的URL,“/*”的意思是对项目根目录下任何资源发出的请求都会被过滤器过滤,也可以用表54中的urlPatterns属性指定,如“@WebFilter(urlPatterns={"/*"})”。@WebFilter注解的常用属性如表54所示。 表54@WebFilter的属性 属性名类型说明 filterNameString用于指定过滤器的名称,默认值是过滤器类的名字 urlPatternsString[]指定一组过滤器的URL匹配模式 valueString[]等价于urlPatterns属性,与urlPatterns属性不能同时使用 servletNamesString[]指定过滤器应用于哪些Servlet。其值是@WebServlet注解的name属性的取值 dispatcherTypesDispatcherType指定过滤器的转发模式。具体取值包括ERROR、FORWARD、INCLUDE、REQUEST initParamsWebInitParam[]指定过滤器的一组初始化参数 表54中,@WebFilter注解有一个dispatcherTypes属性,它可以指定过滤器的转发模式。dispatcherTypes属性有4个常用值。 1) DispatcherType.REQUEST 这个值是dispatcherTyps属性的默认值。当过滤器设置dispatcherTypes属性值为DispatcherType.REQUEST时,过滤器拦截直接URL访问资源的请求与响应,包括: 在地址栏中直接访问、表单提交、超链接、重定向,对于以其他方式发送的请求与响应不予拦截。 2) DispatcherType.FORWARD 当过滤器设置dispatcherTypes属性值为DispatcherType.FORWARD时,过滤器拦截以请求转发方式发送的请求,其他的请求不予拦截。 3) DispatcherType.INCLUDE 当过滤器设置dispatcherTypes属性值为DispatcherType.INCLUDE时,如果用户通过RequestDispatcher对象的include()方法发送请求则被过滤器拦截,对于其他的请求与响应过滤器不予拦截。 4) DispatcherType.ERROR 当过滤器设置dispatcherTypes属性值为DispatcherType.ERROR时,如果通过声明式异常处理机制发送请求,则 被过滤器拦截,其他的请求与响应过滤器不予拦截。 下面,通过几个例子来进一步说明这几个属性值的作用。 【例52】创建一个名为DispatcherTypeFilter的过滤器,针对特定类型的请求进行过滤(拦截)。过滤器代码如文件53所示。 【文件53】DispatcherTypeFilter.java 1package com.example.filter; 2//import部分略 3@WebFilter(urlPatterns="/filter.jsp", 4dispatcherTypes={DispatcherType.ERROR}) 5public class DispatcherTypeFilter extends HttpFilter { 6 7public void doFilter(ServletRequest request, 8ServletResponse response, FilterChain chain) 9throws IOException, ServletException { 10System.out.print("this is dispatcher filter"); 11// pass the request along the filter chain 12chain.doFilter(request, response); 13} 14} 如文件53所示,第3~4行将@WebFilter 注解的dispatcherTypes属性设置为DispatcherType.ERROR。这意味着如果服务器端程序在处理请求过程中抛出异常,则过滤器会被调用。否则,过滤器不会被调用。为验证这个设置能否起作用,需要再创建一个Servlet和一个JSP。 创建一个名为DispatcherTypeServlet的Servlet, 并设定该Servlet在处理请求过程中会抛出运行时异常。DispatcherTypeServlet的实现代码如文件54所示。 【文件54】DispatcherTypeServlet.java 1package com.example.filter; 2//import部分略 3@WebServlet("/DispatcherTypeServlet") 4public class DispatcherTypeServlet extends HttpServlet { 5protected void doGet(HttpServletRequest request, 6HttpServletResponse response) 7throws ServletException, IOException { 8if(true) 9throw new RuntimeException("hello,exception"); 10} 11//此处省略了doPost()方法 12} 创建一个名为filter的JSP,用于输出响应内容。filter.jsp的代码如文件55所示。 【文件55】filter.jsp 1<%@ page contentType="text/html; charset=UTF-8"%> 2<!DOCTYPE html> 3<html> 4<body> 5This is jsp file. 6</body> 7</html> 此外,需要在WEBINF文件夹下配置部署描述文件web.xml,内容如文件56所示。 【文件56】web.xml 1<display-name>Chapter5</display-name> 2<error-page> 3<error-code>500</error-code> 4<location>/filter.jsp</location> 5</error-page> 6<welcome-file-list> 7<welcome-file>index.html</welcome-file> 8</welcome-file-list> 在浏览器的地址栏输入“http://localhost:8080/chapter5/DispatcherTypeServlet”给本例中的Servlet发请求,Servlet收到请求后会抛出一个运行时异常(文件54的第8~9行),而根据部署描述文件web.xml的设置,当服务器端抛出运行时异常,即出现500错误 (Internal Server Error) 时,Servlet容器会将请求转发给filter.jsp(文件56的第2~5行)。而过滤器DispatcherTypeFilter的转发模式为DispatcherType.ERROR(文件53的第3~4行),这个请求就会被过滤器过滤,进而产生相应的输出。浏览器端的输出如图54所示,同时在控制台可见过滤器的输出,如图55所示。 图54浏览器端的输出 图55控制台的输出 如果通过浏览器的地址栏直接给filter.jsp发送请求,由于filter.jsp代码在运行中不会抛出异常,即这个请求不符合过滤器的过滤规则,因此过滤器不会被调用。这样,只能在浏览器页面上看到如图54的JSP的输出。读者不妨分析下,如果将文件54第8行的条件改为永假条件,会有什么样的输出?此外,如果将文件54的 Servlet中的代码改为将请求转发给JSP,即: protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.getRequestDispatcher("filter.jsp") .forward(request, response); } 如将过滤器的转发模式设置为DispatcherType.FORWARD。再次给Servlet发请求,得到的结果同图54和图55。读者可自行分析其原理。 5.1.4过滤器应用案例 【例53】创建一个名为IPFilter的过滤器,实现禁止地址为127.0.0.1的主机访问filter.jsp的功能。 分析: 根据过滤器的特性,某主机给本系统资源发送的请求首先到达过滤器。此时,可以通过过滤器获取远程主机的IP地址,与系统中设定的IP地址黑名单比对。如果该主机的IP地址在黑名单中,则拦截该主机的请求; 否则将请求传递给过滤器链的下一个资源。IPFilter的代码如文件57所示。 【文件57】IPFilter.java 1package com.example.filter; 2//import部分此处略 3 4@WebFilter(urlPatterns="/filter.jsp",initParams={@WebInitParam( 5name="forbid",value="127.0.0.1")}) 6public class IPFilter extends HttpFilter { 7private String address; 8 9public void doFilter(ServletRequest request, 10ServletResponse response, FilterChain chain) 11throws IOException, ServletException { 12PrintWriter out = response.getWriter(); 13if(address.equals(request.getRemoteAddr())) 14out.print("Sorry, you are forbidden to visit"); 15else 16chain.doFilter(request, response); 17} 18public void init(FilterConfig config) { 19address = config.getInitParameter("forbid"); 20} 21} 如文件57所示,过滤器IPFilter利用@WebFilter标签的initParams属性设置了过滤器的初始化参数forbid,并将其初始值设定为 127.0.0.1。随后,利用过滤器的init()方法读取初始化参数。启动Tomcat服务器,在浏览器的地址栏中输入地址http://127.0.0.1:8080/chapter5/filter.jsp,可见利用本机地址访问filter.jsp时,请求被过滤器拦截,浏览器显示结果如图56所示。 图56禁止特定IP访问系统的浏览器显示结果 【例54】防止中文乱码。 在填写表单数据时,难免会需要输入中文,如姓名、公司名称等。可以利用过滤器的特性,在表单提交的请求到达RequestParamServlet前,对请求中的字符设置编码规则。并在RequestParamServlet响应到达客户端前设置响应消息中的字符编码规则。 创建一个名为CharFilter的过滤器,用于解决例35中填写表单数据时遇到的中文乱码问题,其中请求和响应消息的字符编码均设置为UTF8。CharFilter的代码如文件58所示。 【文件58】CharFilter.java 1import java.io.IOException; 2 3//import部分此处略 4 5@WebFilter("/RequestParamServlet") 6public class CharFilter extends HttpFilter { 7 8public void doFilter(jakarta.servlet.ServletRequest request, 9jakarta.servlet.ServletResponse response, FilterChain chain) 10throws IOException, ServletException { 11request.setCharacterEncoding("UTF-8"); 12response.setCharacterEncoding("UTF-8"); 13chain.doFilter(request, response); 14} 15} 5.2监听器 〖*2〗5.2.1监听器概述 在Web应用程序设计中,经常需要对某些事件进行监听,以便及时做出处理。对于桌面应用程序而言,鼠标单击或双击、键盘上的键被按下等都是事件。类似地,对于Web应用程序来说,session对象的创建、请求域中某个属性的移除等都是事件。为此,Servlet规范提供了监听器(Listener),专门用于监听Servlet事件。监听器技术涉及几个重要的概念,分别如下。 (1) 事件: 对于Web应用程序而言,ServletContext对象、HttpSession对象和ServletRequest对象的状态改变可称为Servlet事件。如HttpSession对象的创建,ServletRequest对象中属性的增加或移除都是事件。 (2) 监听器: 负责监听事件是否发生。它是一个实现了一个或多个Servlet事件监听接口的类。它在Web应用程序部署时 被注册到Web容器中并被实例化。 (3) 事件处理器: 监听器的方法。当事件发生的时候,监听器会监听到事件的发生,并触发相应的处理器用以处理事件。 5.2.2监听器编程接口 Web应用程序中的监听器就是一段实现了特定接口的Java程序,专门用于监听Web应用程序中ServletContext、HttpSession和ServletRequest等域对象的状态变化,包括这些对象的创建、销毁及域对象属性的修改。 根据监听对象的不同,监听器可以划分为以下3种。 (1) ServletContext监听器: 用于监听ServletContext对象。 (2) HttpSession监听器: 用于监听HttpSession对象。 (3) ServletRequest监听器: 用于监听ServletRequest 对象。 这3种监听器共包含9个监听器接口和7个监听事件类,如表55所示。 表55监听器接口与事件类 监 听 对 象监听器接口监听事件类说明 ServletContext ServletContextListenerServletContextEvent用于监听ServletContext对象的创建和销毁 ServletContextAttributeListenerServletContextAttributeEvent用于监听ServletContext对象中属性的变更 HttpSession HttpSessionListener HttpSessionActivation Listener HttpSessionAttribute Listener HttpSessionBinding Listener HttpSessionId Listener HttpSessionEvent HttpSessionBinding Event HttpSessionEvent 用于监听HttpSession对象的创建和销毁 用于监听HttpSession对象被钝化和激活的事件 用于监听HttpSession对象中属性的变更 用于监听HttpSession对象中JavaBean对象的绑定和解绑事件 用于监听HttpSession对象的id的变更 ServletRequest ServletRequestListenerServletRequestEvent用于监听ServletRequest对象的创建和销毁 ServletRequestAttribute ListenerSevletRequestAttribute Event用于监听ServletRequest对象中属性的变更 1. 监听ServletContext对象 用于监听ServletContext对象的接口有两个。其中,ServletContextListener接口用于监听ServletContext对象的创建和销毁,ServletContextAttributeListener用于监听ServletContext对象中属性的变化(添加、移除)。常用的监听方法如表56所示。 表56SevletContext监听器的接口和常用方法 接 口 名 称接 口 方 法事件 ServletContextListener void contextInitialized(ServletContext Event sce)创建ServletContext对象 void contextDestroyed(ServletContext Event sce)销毁ServletContext对象 ServletContextAttributeListener void attributeAdded(ServletContext AttributeEvent event)添加属性 void attributeRemoved(ServletContext AttributeEvent event)删除属性 void attributeReplaced(ServletContext AttributeEvent event)修改属性 2. 监听HttpSession对象 用于监听HttpSession对象的接口有5个。这些接口对应的常用方法如表57所示。 表57HttpSession监听器的接口和常用方法 接 口 名 称接 口 方 法事件 HttpSessionListener void sessionCreated(HttpSessionEvent se)创建HttpSession对象 void sessionDestroyed(HttpSessionEvent se)销毁HttpSession对象 HttpSessionActivation Listener void sessionWillPassivate(HttpSessionEvent se)HttpSession对象被激活 void sessionDidActivate(HttpSessionEvent se)HttpSession对象被钝化 HttpSessionAttribute Listener void attributeAdded(HttpSessionBindingEvent event)增加属性 void attributeRemoved(HttpSessionBindingEvent event)移除属性 void attributeReplaced(HttpSessionBindingEvent event)修改属性 HttpSessionBinding Listener void valueBound(HttpSessionBindingEvent event)HttpSession对象绑定 void valueUnbound(HttpSessionBindingEvent event)HttpSession对象解绑 HttpSessionIdListenervoid sessionIdChanged(HttpSessionEvent event,String oldSessionId)sessionid变化 说明: 激活(Activate)和钝化(Passivate)是Web容器为了更好地利用系统资源或者进行服务器负载均衡而对特定对象采取的措施。会话对象的钝化指的是暂时将会话对象通过序列化的方法存储到硬盘上,而激活与钝化相反,指的是把硬盘上存储的会话对象重新加载到Web容器中。 3. 监听ServletRequest对象 用于监听ServletRequest对象的接口有两个,这些接口对应的常用方法如表58所示。 表58ServletRequest监听器的接口和常用方法 接 口 名 称接 口 方 法事件 ServletRequestListener void requestInitialized(ServletRequestEvent sre)创建ServletRequest对象 void requestDestroyed(ServletRequestEvent sre)销毁ServletRequest对象 ServletRequestAttributeListener void attributeAdded(ServletRequestAttributeEvent srae)添加属性 void attributeRemoved(ServletRequestAttributeEvent srae)移除属性 void attributeReplaced(ServletRequestAttributeEvent srae)修改属性 5.2.3监听器应用案例 设计一个监听器一般需要如下步骤。 (1) 实现合适的接口: 监听器需要根据监听对象的不同,实现表55中的某个监听接口。 (2) 设计事件处理器: 根据所选择的监听器接口,实现该接口中的相关方法。 (3) 配置监听器: 既可以在部署描述文件web.xml文件中配置,又可以利用注解@WebListener完成监听器配置。 (4) 提供任何需要的初始化参数。 【例55】设计监听器,监听ServletContext、HttpSession和ServletRequest域对象的生命周期事件。 为实现这个目标,就要设计监听器类来实现针对这些域对象的监听器接口。可以设计一个类,来实现3个接口,从而使这个类具有针对3个域对象的事件监听的功能。监听器MyListener代码如文件59所示。 【文件59】MyListener.java 1package com.example.listener; 2//import部分此处略 3 4@WebListener 5public class MyListener implements ServletContextListener, 6HttpSessionListener, ServletRequestListener { 7public void contextInitialized(ServletContextEvent sce) { 8System.out.println("ServletContext对象被创建"); 9} 10public void contextDestroyed(ServletContextEvent sce) { 11System.out.println("ServletContext对象被销毁"); 12} 13public void sessionCreated(HttpSessionEvent se) { 14System.out.println("HttpSession对象被创建"); 15} 16public void sessionDestroyed(HttpSessionEvent se) { 17System.out.println("HttpSession对象被销毁"); 18} 19public void requestInitialized(ServletRequestEvent sre) { 20System.out.println("ServletRequest对象被创建"); 21} 22public void requestDestroyed(ServletRequestEvent sre) { 23System.out.println("ServletRequest对象被销毁"); 24} 25} 文件59中,@WebListener的作用是配置监听器(第4行)。ServletContext对象与当前的Web应用程序对应。因为已在Tomcat服务器上部署了chapter5项目,当服务器启动时,Tomcat服务器会自动加载chapter5项目,并创建与其对应的ServletContext对象。由于chapter5项目中配置了MyListener监听器,并且该监听器实现了ServletContextListener接口,当Tomcat创建ServletContext对象时就会调用MyListener类的contextInitialized()方法作为事件处理器,输出“ServletContext对象被创建”这行信息(第7~9行)。要观察ServletContext对象的销毁信息,可以将Tomcat服务器关闭。Tomcat服务器关闭前,会销毁ServletContext对象,同时contextDestroyed()方法被调用(第10~12行),在控制台可见ServletContext对象被销毁的信息,如图57所示。 图57ServletContext对象的创建和销毁 为了查看HttpSessionListener和ServletRequestListener的运行效果,可以在chapter5项目中编写一个名为listener.jsp的文件,内容如文件510所示。 【文件510】listener.jsp 1<html> 2<body> 3This is listener 4</body> 5</html> 为了尽快看到HttpSession对象的创建和销毁过程,可以在项目的web.xml文件中设置session的超时时间为1min,内容如下: <session-config> <session-timeout>1</session-timeout> </session-config> 启动Tomcat服务器,在浏览器的地址栏输入“http://localhost:8080/chapter5/listener.jsp”, 观察控制台输出。当浏览器第一次访问项目中的动态资源(JSP、Servlet)时,会创建HttpSession对象,Tomcat服务器会调用监听器的sessionCreated()方法作为创建HttpSession对象事件的事件处理器(第13~15行)。并且,由于浏览器发送请求,会自动创建HttpServletRequest对象,Tomcat服务器会调用requestInitialized()方法作为创建请求对象事件的处理器(第19~21行)。当listener.jsp对该请求做出响应后,请求随即被销毁,控制台就会输出“ServletRequest对象被销毁”的消息(第22~24行),如图58所示。 图58控制台输出 如果关闭访问listener.jsp文件的浏览器页面或保持浏览器不刷新。与之对应的HttpSession对象会在1min后被销毁。控制台显示结果如图58所示。