会话、过滤器和监听器 约 5037 个字 635 行代码 21 张图片 预计阅读时间 25 分钟
会话 前面学习到的HTTP协议属于无状态协议,所谓无状态就是不保存状态,即无状态协议,HTTP协议自身不对请求和响应之间的通信状态进行保存,也就是说,在HTTP协议这个级别,协议对于发送过的请求或者响应都不做持久化处理。简单理解就是:浏览器发送请求,服务器接收并响应,但是服务器不记录请求是否来自哪个浏览器,服务器没记录浏览器的特征,也就是客户端的状态
为了解决上面的问题,就需要使用到会话管理,在Web项目中,进行会话管理时使用到Cookie和Session,二者配合实现会话管理。其中Cookie是在客户端保留少量数据的技术,主要通过响应头向客户端响应一些客户端要保留的信息,而Session是在服务端保留更多数据的技术,主要通过HttpSession
对象保存一些和客户端相关的信息
Cookie介绍和使用 Cookie是一种客户端会话技术,Cookie由服务端产生,它是服务器存放在浏览器的一小份数据,浏览器以后每次访问该服务器的时候都会将这小份数据携带到服务器去
基本过程如下:
服务端创建Cookie,将Cookie放入响应对象中,Tomcat容器将Cookie转化为Set-Cookie
响应头,响应给客户端。而客户端在收到Cookie的响应头时,在下次请求该服务的资源会以Cookie请求头的形式携带之前收到的Cookie
Cookie是一种键值对格式的数据,从tomcat8.5开始可以保存中文,但是不推荐。由于Cookie是存储于客户端的数据,比较容易暴露,一般不存储一些敏感或者影响安全的
Cookie原理图如下:
Cookie应用场景举例
记录用户名:当在用户名的输入框中输入完用户名后,浏览器记录用户名,下一次再访问登录页面时,用户名自动填充到用户名的输入框
保存电影播放进度:在网页上播放电影的时候,如果中途退出浏览器了,下载再打开浏览器播放同一部电影的时候,会自动跳转到上次退出时候的进度,因为在播放的时候会将播放进度保存到Cookie中
在Tomcat中实现Cookie可以使用下面的方式:
通过Cookie
的有参构造函数创建Cookie
对象,其中传递两个参数:第一个参数表示键,第二个参数表示值 调用HttpServletResponse
对象的setCookie
方法,将Cookie
对象作为实参传递给该方法 例如下面的代码:
Java 1
2
3
4
5
6
7
8
9
10
11
12 @WebServlet ( "/servletA" )
public class ServletA extends HttpServlet {
@Override
protected void service ( HttpServletRequest req , HttpServletResponse resp ) throws ServletException , IOException {
// 创建Cookie
Cookie cookie1 = new Cookie ( "c1" , "c1_message" );
Cookie cookie2 = new Cookie ( "c2" , "c2_message" );
// 将cookie放入响应对象
resp . addCookie ( cookie1 );
resp . addCookie ( cookie2 );
}
}
配置Tomcat并运行即可在浏览器的响应头中看到对应的Set-Cookie
:
接着创建一个ServletB
通过同一个客户端向服务器端发起请求,可以看到在请求头中存在刚才在ServletA
中设置的Cookie
。在ServletB
中想要获取到请求体携带的Cookie
可以调用HttpServletRequest
对象的getCookies()
方法获取到对应的Cookie
数组,遍历数组可以拿到对应的键值对对象,通过该对象调用getName()
和getValue()
即可获取到对应的键和值
Note
需要注意,如果请求头中没有Cookie
,那么调用getCookies
方法会返回null
而不是返回一个空数组,所以在迭代Cookie
数组时需要判断Cookie
数组引用是否为null
,否则会产生NullPointerException
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14 @WebServlet ( "/servletB" )
public class ServletB extends HttpServlet {
@Override
protected void service ( HttpServletRequest req , HttpServletResponse resp ) throws ServletException , IOException {
//获取请求中的cookie
Cookie [] cookies = req . getCookies ();
//迭代cookies数组
if ( null != cookies && cookies . length != 0 ) {
for ( Cookie cookie : cookies ) {
System . out . println ( cookie . getName () + ":" + cookie . getValue ());
}
}
}
}
配置Tomcat并运行即可在浏览器的请求头中看到对应的Cookie
:
Cookie的时效性 Cookie一共分为两种:
会话级Cookie,特点是:服务器端并没有明确指定Cookie的存在时间,客户端Cookie数据存在于内存中。只要当前是同一个客户端,Cookie数据就一直都在,如果客户端关闭,内存中的Cookie数据就会被释放 持久化Cookie,特点是:服务器端明确设置了Cookie的存在时间,客户端Cookie数据会被保存到硬盘上。Cookie在硬盘上存在的时间根据服务器端限定的时间来管控,不受浏览器关闭的影响,只要到达了预设的时间,该Cookie就会被释放 默认情况下Cookie的有效期是一次会话范围(即同一个客户端发起请求)内,可以通过Cookie对象的setMaxAge()
方法让Cookie持久化保存到浏览器上
Note
需要注意,setMaxAge()
方法默认的时间单位是秒。如果设置参数为0,表示将浏览器中保存的该Cookie删除
例如使用ServletA
创建一个持久化Cookie:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14 @WebServlet ( "/servletA" )
public class ServletA extends HttpServlet {
@Override
protected void service ( HttpServletRequest req , HttpServletResponse resp ) throws ServletException , IOException {
// 创建Cookie
Cookie cookie1 = new Cookie ( "c1" , "c1_message" );
// 设置Cookie有效时间为1分钟
cookie1 . setMaxAge ( 60 );
Cookie cookie2 = new Cookie ( "c2" , "c2_message" );
// 将cookie放入响应对象
resp . addCookie ( cookie1 );
resp . addCookie ( cookie2 );
}
}
配置Tomcat并运行即可在浏览器的响应头中看到对应的Set-Cookie
:
同样使用ServletB
再次向服务器发起请求,但是此时先关闭一次浏览器,再请求:
Cookie的提交路径 访问互联网资源时不是每次都需要把所有Cookie带上。访问不同的资源时,可以携带不同的cookie,在代码中可以通过Cookie对象的setPath(String path)
方法对Cookie提交的路径进行设置
此时路径的写法与重定向的路径 一致,下面以绝对路径为例:
在ServletA
中设置Cookie的提交路径:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13 public class ServletA extends HttpServlet {
@Override
protected void service ( HttpServletRequest req , HttpServletResponse resp ) throws ServletException , IOException {
// 创建Cookie
Cookie cookie1 = new Cookie ( "c1" , "c1_message" );
// 设置cookie的提交路径
cookie1 . setPath ( "/web03_war_exploded/servletB" );
Cookie cookie2 = new Cookie ( "c2" , "c2_message" );
// 将cookie放入响应对象
resp . addCookie ( cookie1 );
resp . addCookie ( cookie2 );
}
}
配置Tomcat并运行即可在浏览器的响应头中看到对应的Set-Cookie
:
接着通过访问ServletB
向服务器发起请求时可以看到携带的Cookie(cookie1
和cookie2
):
如果使用其他任意的资源请求浏览器,则看不到对应的Cookie(只有cookie2
):
Session介绍和使用 Session
对象属于HttpSession
,HttpSession
是一种保留更多信息在服务端的一种技术,服务器会为每一个客户端开辟一块内存空间,即Session
对象,客户端在发送请求时,都可以使用自己的Session,这样服务端就可以通过Session
对象来记录某个客户端的状态
服务端在为客户端创建Session时,会同时将Session
对象的ID(即JSESSIONID
)以Cookie
的形式放入响应对象。后端创建完Session后,客户端会收到一个特殊的Cookie,叫做JSESSIONID
,当客户端下一次请求时,就会携带JSESSIONID
,后端收到后,根据JSESSIONID
找到对应的Session
对象通过该机制,服务端通过Session
对象就可以存储一些专门针对某个客户端的信息
原理图如下:
Session应用场景举例
记录用户的登录状态:用户登录后,将用户的账号等敏感信息存入Session 记录用户操作的历史:例如记录用户的访问痕迹,用户的购物车信息等临时性的信息 使用HttpSession
的步骤如下:
调用HttpServletRequest
对象的getSession()
方法获取到Session
对象 调用Session
对象的getId()
方法可以获取到已经创建的Session
对象的JSESSIONID
,通过Session
对象isNew()
方法可以判断指定的Session
对象是否是最新的 因为Session
对象是三大域对象之一,所以也有三大域对象共有的方法
例如下面的代码,创建一个ServletA
向Session对象中存储数据:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 @WebServlet ( "/servletA" )
public class ServletA extends HttpServlet {
@Override
protected void service ( HttpServletRequest req , HttpServletResponse resp ) throws ServletException , IOException {
// 获取请求中的参数
String username = req . getParameter ( "username" );
// 获取session对象
HttpSession session = req . getSession ();
// 获取Session的ID
String jSessionId = session . getId ();
System . out . println ( jSessionId );
// 判断session是不是新创建的session
boolean isNew = session . isNew ();
System . out . println ( isNew );
// 向session对象中存入数据
session . setAttribute ( "username" , username );
}
}
配置Tomcat并运行即可在浏览器的响应头中看到对应的Set-Cookie
:
创建一个ServletB
,从Session
对象中获取到对应的信息:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 @WebServlet ( "/servletB" )
public class ServletB extends HttpServlet {
@Override
protected void service ( HttpServletRequest req , HttpServletResponse resp ) throws ServletException , IOException {
// 获取session对象
HttpSession session = req . getSession ();
// 获取Session的ID
String jSessionId = session . getId ();
System . out . println ( jSessionId );
// 判断session是不是新创建的session
boolean isNew = session . isNew ();
System . out . println ( isNew );
// 从session中取出数据
String username = ( String ) session . getAttribute ( "username" );
System . out . println ( username );
}
}
配置Tomcat并运行即可在浏览器的请求头中看到对应的Cookie
:
isNew()
方法是如何判断指定的Session
对象是否是最新的取决于getSession()
对象的处理逻辑,流程图如下:
Session的时效性 Session之所以需要时效性,是因为用户量很大之后,Session
对象相应的也要创建很多。如果只创建不释放,那么服务器端的内存迟占用高,并且因为客户端关闭行为无法被服务端直接侦测,或者客户端较长时间不操作也经常出现,类似这些的情况也会导致内存占用高,所以就需要对Session的时限进行设置
默认的Session最大闲置时间(两次使用同一个Session中的间隔时间)在tomcat/conf/web.xml
配置为30分钟
可以自己在当前项目的web.xml
对最大闲置时间进行重新设定:
也可以通过HttpSession
对象的API对最大闲置时间进行设定:
Java // 设置最大闲置时间
session . setMaxInactiveInterval ( 60 );
也可以直接让Session对象失效:
Java // 直接让session失效
session . invalidate ();
三大域对象 前面介绍过什么是域对象,并且已经提过最大范围的应用域对象:ServletContext
,接下来就是剩下的两个域对象:
会话域对象:HttpSession
请求域对象:HttpServletRequest
三大域对象的数据作用范围图解:
请求域对象(每一次请求或者请求转发时创建,作用范围最小):
会话域对象(每一次会话时创建,作用范围大于请求域对象,但是小于应用域对象):
应用域对象(每一次启动Tomcat时由Tomcat自动创建,作用范围最大):
三大域对象作用范围综合图解:
因为都是域对象,所以共有下面的API:
API 功能 void setAttribute(String name,String value)
向域对象中添加/修改数据 Object getAttribute(String name);
从域对象中获取数据 removeAttribute(String name);
移除域对象中的数据
创建一个ServletA
,向三大域对象中存放数据:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 @WebServlet ( "/servletA" )
public class ServletA extends HttpServlet {
@Override
protected void service ( HttpServletRequest req , HttpServletResponse resp ) throws ServletException , IOException {
// 向请求域中放入数据
req . setAttribute ( "request" , "request-message" );
// 请求转发的方式
//req.getRequestDispatcher("servletB").forward(req,resp);
// 向会话域中放入数据
HttpSession session = req . getSession ();
session . setAttribute ( "session" , "session-message" );
// 向应用域中放入数据
ServletContext application = getServletContext ();
application . setAttribute ( "application" , "application-message" );
}
}
创建一个ServletB
,向三大域对象中取出数据:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 @WebServlet ( "/servletB" )
public class ServletB extends HttpServlet {
@Override
protected void service ( HttpServletRequest req , HttpServletResponse resp ) throws ServletException , IOException {
// 从请求域中获取数据
String reqMessage = ( String ) req . getAttribute ( "request" );
System . out . println ( reqMessage );
// 从会话域中获取数据
HttpSession session = req . getSession ();
String sessionMessage = ( String ) session . getAttribute ( "session" );
System . out . println ( sessionMessage );
// 从应用域中获取数据
ServletContext application = getServletContext ();
String applicationMessage = ( String ) application . getAttribute ( "application" );
System . out . println ( applicationMessage );
}
}
三大域对象的使用场景:
请求域对象:请求转发时,请求域可以传递数据请求域内一般放本次请求业务有关的数据,如:查询到的所有的部门信息 会话域对象:同一个会话内,不用请求转发,会话域可以传递数据会话域内一般放本次会话的客户端有关的数据,如:当前客户端登录的用户 应用域对象:同一个APP内,不同的客户端,应用域可以传递数据应用域内一般放本程序应用有关的数据,如:Spring框架的IOC容器 过滤器 过滤器介绍和使用 Filter即过滤器,是JAVAEE技术规范之一,作用目标资源的请求进行过滤的一套技术规范,是JavaWeb项目中最为实用的技术之一
Filter
接口定义了过滤器的开发规范,所有的过滤器都要实现该接口。Filter
的工作位置是项目中所有目标资源之前,容器在创建HttpServletRequest
和HttpServletResponse
对象后,会先调用Filter
的doFilter
方法。Filter
的doFilter
方法可以控制请求是否继续,如果放行,则请求继续,否则请求到此为止,此时过滤器本身做出响应。Filter
不仅可以对请求做出过滤,也可以在目标资源做出响应前,对响应再次进行处理
Filter
是GOF中责任链模式的典型案例,Filter
的常用应用包括但不限于:登录权限检查、解决网站乱码、过滤敏感字符、日志记录、性能分析等
过滤器在开发中的应用场景
日志的记录、性能的分析、乱码的处理、事务的控制、登录的控制、跨域的处理等
过滤器工作位置图解:
根据Filter接口源码:
Java public interface Filter {
default public void init ( FilterConfig filterConfig ) throws ServletException {
}
public void doFilter ( ServletRequest request , ServletResponse response , FilterChain chain )
throws IOException , ServletException ;
default public void destroy () {
}
}
常见的API有如下三个:
API 目标 default public void init(FilterConfig filterConfig)
初始化方法,由容器调用并传入初始配置信息filterConfig
对象 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
过滤方法、核心方法、过滤请求、决定是否放行等响应之前的其他处理等都在该方法中 default public void destroy()
销毁方法,容器在回收过滤器对象之前调用的方法
例如开发一个日志记录过滤器,其功能如下:
用户请求到达目标资源之前,记录用户的请求资源路径 响应之前记录本次请求目标资源运算的耗时 可以选择将日志记录进入文件,为了方便测试,这里将日志直接在控制台打印 代码如下:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 public class LoggingFilter implements Filter {
// 创建日期格式对象
private SimpleDateFormat dateFormat = new SimpleDateFormat ( "yyyy-MM-dd HH:mm:ss" );
@Override
public void doFilter ( ServletRequest servletRequest , ServletResponse servletResponse , FilterChain filterChain ) throws IOException , ServletException {
// 参数父转子
HttpServletRequest request = ( HttpServletRequest ) servletRequest ;
HttpServletResponse response = ( HttpServletResponse ) servletResponse ;
// 拼接日志文本
String requestURI = request . getRequestURI ();
String time = dateFormat . format ( new Date ());
String beforeLogging = requestURI + "在" + time + "被请求了" ;
// 打印日志
System . out . println ( beforeLogging );
// 获取系统时间
long t1 = System . currentTimeMillis ();
// 放行请求
filterChain . doFilter ( request , response );
// 获取系统时间
long t2 = System . currentTimeMillis ();
// 拼接日志文本
String afterLogging = requestURI + "在" + time + "的请求耗时:" + ( t2 - t1 ) + "毫秒" ;
// 打印日志
System . out . println ( afterLogging );
}
}
在上面的代码中,doFilter
方法中的请求和响应对象是以父接口的形式声明的,实际传入的实参就是HttpServletRequest
和HttpServletResponse
子接口级别的,可以安全强转。filterChain.doFilter(request,response);
这行代码的功能是放行请求,如果没有这一行代码,则请求到此为止,filterChain.doFilter(request,response);
在放行时需要传入request
和response
,意味着请求和响应对象要继续传递给后续的资源,这里没有产生新的HttpServletRequest
对象和HttpServletResponse
对象
为了测试上面的过滤器,下面定义两个Servlet
用于测试:
为了可以让过滤器生效,需要在web.xml
文件中配置Filter
需要在哪些资源前进行过滤,配置方式与配置Servlet
基本类似:
XML 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 <?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns= "https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation= "https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version= "5.0" >
<!--配置filter,并为filter起别名-->
<filter>
<filter-name> loggingFilter</filter-name>
<filter-class> com.atguigu.filters.LoggingFilter</filter-class>
</filter>
<!--为别名对应的filter配置要过滤的目标资源-->
<filter-mapping>
<filter-name> loggingFilter</filter-name>
<!--通过映射路径确定过滤资源-->
<url-pattern> /servletA</url-pattern>
<!--通过后缀名确定过滤资源-->
<url-pattern> *.html</url-pattern>
<!--通过servlet别名确定过滤资源-->
<servlet-name> servletBName</servlet-name>
</filter-mapping>
</web-app>
在上面的内容中,filter-mapping
标签中定义了过滤器对那些资源进行过滤,子标签url-pattern
通过映射路径确定过滤范围,除了可以使用url-pattern
以外,还可以使用servlet-name
指定过滤资源,二者的区别就是url-pattern
可以写静态资源和动态资源的路径,而servlet-name
中就只能写指定的Servlet
的name
属性值。一个filter-mapping
下可以配置多个url-pattern
,也可以定义多个servlet-name
,并且二者可以同时存在
过滤过程如下图所示:
过滤器的生命周期 过滤器作为web项目的组件之一,和Servlet
的生命周期类似,但又略有不同,例如没有Servlet
的load-on-startup
的配置,默认就是系统启动立刻构造
控制声明周期和执行次数如下表所示:
阶段 对应方法 执行时机 执行次数 创建对象 构造器 Web应用启动时 1 初始化方法 void init(FilterConfig filterConfig)
构造完毕 1 过滤请求 void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
每次请求 多次 销毁 default void destroy()
Web应用关闭时 1次
测试代码如下:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 @WebServlet ( "/*" )
public class LifeCycleFilter implements Filter {
public LifeCycleFilter (){
System . out . println ( "LifeCycleFilter constructor method invoked" );
}
@Override
public void init ( FilterConfig filterConfig ) throws ServletException {
System . out . println ( "LifeCycleFilter init method invoked" );
}
@Override
public void doFilter ( ServletRequest servletRequest , ServletResponse servletResponse , FilterChain filterChain ) throws IOException , ServletException {
System . out . println ( "LifeCycleFilter doFilter method invoked" );
filterChain . doFilter ( servletRequest , servletResponse );
}
@Override
public void destroy () {
System . out . println ( "LifeCycleFilter destroy method invoked" );
}
}
接着创建一个Servlet1作为测试:
Java @WebServlet ( value = "/s1" , name = "servlet1" )
public class Servlet1 extends HttpServlet {
@Override
protected void service ( HttpServletRequest req , HttpServletResponse resp ) throws ServletException , IOException {
System . out . println ( "执行Servlet1" );
}
}
对应的web.xml
文件如下:
XML 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 <?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns= "https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation= "https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version= "6.0" >
<filter>
<filter-name> lifeCycleFilter</filter-name>
<filter-class> LifeCycleFilter</filter-class>
</filter>
<filter-mapping>
<filter-name> lifeCycleFilter</filter-name>
<servlet-name> servlet1</servlet-name>
</filter-mapping>
</web-app>
配置Tomcat后启动查看控制台,先看到:
Text Only LifeCycleFilter init method invoked
LifeCycleFilter constructor method invoked
接着请求Servlet1,可以看到:
Text Only LifeCycleFilter doFilter method invoked
执行Servlet1
接着终止Tomcat可以看到:
Text Only LifeCycleFilter destroy method invoked
过滤器链的使用 一个Web项目中,可以同时定义多个过滤器,多个过滤器对同一个资源进行过滤时,工作位置有先后,整体形成一个工作链,称之为过滤器链
存在过滤器链时,过滤器执行顺序如下:
过滤器链中的过滤器的顺序由filter-mapping
顺序决定 每个过滤器过滤的范围不同,针对同一个资源来说,过滤器链中的过滤器个数可能是不同的 如果某个Filter
是使用servlet-name
进行匹配规则的配置,那么这个Filter
执行的优先级要更低 过滤器链执行过程:
过滤器链测试:
过滤器1 过滤器2 过滤器3
Java public class Filter1 implements Filter {
@Override
public void doFilter ( ServletRequest servletRequest , ServletResponse servletResponse , FilterChain filterChain ) throws IOException , ServletException {
System . out . println ( "filter1 before chain.doFilter code invoked" );
filterChain . doFilter ( servletRequest , servletResponse );
System . out . println ( "filter1 after chain.doFilter code invoked" );
}
}
Java public class Filter2 implements Filter {
@Override
public void doFilter ( ServletRequest servletRequest , ServletResponse servletResponse , FilterChain filterChain ) throws IOException , ServletException {
System . out . println ( "filter2 before chain.doFilter code invoked" );
filterChain . doFilter ( servletRequest , servletResponse );
System . out . println ( "filter2 after chain.doFilter code invoked" );
}
}
Java public class Filter3 implements Filter {
@Override
public void doFilter ( ServletRequest servletRequest , ServletResponse servletResponse , FilterChain filterChain ) throws IOException , ServletException {
System . out . println ( "filter3 before chain.doFilter code invoked" );
filterChain . doFilter ( servletRequest , servletResponse );
System . out . println ( "filter3 after chain.doFilter code invoked" );
}
}
用于测试的Servlet
代码如下:
Java @WebServlet ( "/sc" )
public class ServletC extends HttpServlet {
@Override
protected void service ( HttpServletRequest req , HttpServletResponse resp ) throws ServletException , IOException {
System . out . println ( "servletC service method invoked" );
}
}
过滤器的配置文件如下:
XML 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 <?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns= "https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation= "https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
version= "5.0" >
<filter>
<filter-name> filter1</filter-name>
<filter-class> com.atguigu.filters.Filter1</filter-class>
</filter>
<filter>
<filter-name> filter2</filter-name>
<filter-class> com.atguigu.filters.Filter2</filter-class>
</filter>
<filter>
<filter-name> filter3</filter-name>
<filter-class> com.atguigu.filters.Filter3</filter-class>
</filter>
<!--filter-mapping的顺序决定了过滤器的工作顺序-->
<filter-mapping>
<filter-name> filter1</filter-name>
<url-pattern> /servletC</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name> filter2</filter-name>
<url-pattern> /servletC</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name> filter3</filter-name>
<url-pattern> /servletC</url-pattern>
</filter-mapping>
</web-app>
上面的测试工作流程如下图所示:
使用@WebFilter
配置过滤器 与配置Servlet
一样,可以使用指定的注解@WebFilter
对过滤器进行配置,下面是WebFilter
注解的部分源码:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 package jakarta.servlet.annotation ;
import jakarta.servlet.DispatcherType ;
import java.lang.annotation.Documented ;
import java.lang.annotation.ElementType ;
import java.lang.annotation.Retention ;
import java.lang.annotation.RetentionPolicy ;
import java.lang.annotation.Target ;
@Target ({ ElementType . TYPE })
@Retention ( RetentionPolicy . RUNTIME )
@Documented
public @interface WebFilter {
// ...
WebInitParam [] initParams () default {};
String filterName () default "" ;
// ...
String [] servletNames () default {};
String [] value () default {};
String [] urlPatterns () default {};
// ...
}
以下面的过滤器配置文件web.xml
内容为例:
XML 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 <!--配置filter,并为filter起别名-->
<filter>
<filter-name> loggingFilter</filter-name>
<filter-class> com.atguigu.filters.LoggingFilter</filter-class>
<!--配置filter的初始参数-->
<init-param>
<param-name> dateTimePattern</param-name>
<param-value> yyyy-MM-dd HH:mm:ss</param-value>
</init-param>
</filter>
<!--为别名对应的filter配置要过滤的目标资源-->
<filter-mapping>
<filter-name> loggingFilter</filter-name>
<!--通过映射路径确定过滤资源-->
<url-pattern> /servletA</url-pattern>
<!--通过后缀名确定过滤资源-->
<url-pattern> *.html</url-pattern>
<!--通过servlet别名确定过滤资源-->
<servlet-name> servletBName</servlet-name>
</filter-mapping>
转换为注解如下:
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 @WebFilter (
filterName = "loggingFilter" ,
initParams = { @WebInitParam ( name = "dateTimePattern" , value = "yyyy-MM-dd HH:mm:ss" )},
urlPatterns = { "/servletA" , "*.html" },
servletNames = { "servletBName" }
)
public class LoggingFilter implements Filter {
private SimpleDateFormat dateFormat ;
/*
* init初始化方法,通过filterConfig获取初始化参数
* init方法中,可以用于定义一些其他初始化功能代码
*/
@Override
public void init ( FilterConfig filterConfig ) throws ServletException {
// 获取初始参数
String dateTimePattern = filterConfig . getInitParameter ( "dateTimePattern" );
// 初始化成员变量
dateFormat = new SimpleDateFormat ( dateTimePattern );
}
@Override
public void doFilter ( ServletRequest servletRequest , ServletResponse servletResponse , FilterChain filterChain ) throws IOException , ServletException {
// 参数父转子
HttpServletRequest request = ( HttpServletRequest ) servletRequest ;
HttpServletResponse response = ( HttpServletResponse ) servletResponse ;
// 拼接日志文本
String requestURI = request . getRequestURI ();
String time = dateFormat . format ( new Date ());
String beforeLogging = requestURI + "在" + time + "被请求了" ;
// 打印日志
System . out . println ( beforeLogging );
// 获取系统时间
long t1 = System . currentTimeMillis ();
// 放行请求
filterChain . doFilter ( request , response );
// 获取系统时间
long t2 = System . currentTimeMillis ();
String afterLogging = requestURI + "在" + time + "的请求耗时:" + ( t2 - t1 ) + "毫秒" ;
// 打印日志
System . out . println ( afterLogging );
}
}
Note
注意,如果使用注解方式配置过滤器链时,其执行顺序是按照过滤器名称的字典顺序进行执行
监听器 监听器介绍 监听器:专门用于对域对象对象身上发生的事件或状态改变进行监听和相应处理的对象
监听器是GOF设计模式中,观察者模式的典型案例。所谓观察者模式,就是当被观察的对象发生某些改变时,观察者自动采取对应的行动的一种设计模式。监听器使用的感受类似JavaScript中的事件,被观察的对象发生某些情况时自动触发代码的执行,不同的是监听器并不监听Web项目中的所有组件,仅仅是对三大域对象做相关的事件监听
Web中定义八个监听器接口作为监听器的规范,这八个接口按照不同的标准可以形成不同的分类。对于三大域对象,监听器的分类如下:
按监听的对象划分:
应用域监听器:ServletContextListener
和ServletContextAttributeListener
会话域域监听器:HttpSessionListener
、HttpSessionAttributeListener
、HttpSessionBindingListener
和HttpSessionActivationListener
请求域监听器:ServletRequestListener
和ServletRequestAttributeListener
按监听的事件划分:
域对象的创建和销毁监听器:ServletContextListener
、HttpSessionListener
和ServletRequestListener
域对象数据增删改事件监听器:ServletContextAttributeListener
、HttpSessionAttributeListener
和ServletRequestAttributeListener
其他监听器:HttpSessionBindingListener
和HttpSessionActivationListener
应用域监听器 ServletContextListener
:用于监听ServletContext
对象的创建与销毁
方法名 作用 contextInitialized(ServletContextEvent sce)
ServletContext
创建时调用 contextDestroyed(ServletContextEvent sce)
ServletContext
销毁时调用
其中,ServletContextEvent
对象代表从ServletContext
对象身上捕获到的事件,通过这个事件对象我们可以获取到ServletContext
对象
ServletContextAttributeListener
:用于监听ServletContext
中属性的添加、移除和修改
方法名 作用 attributeAdded(ServletContextAttributeEvent scab)
向ServletContext
中添加属性时调用 attributeRemoved(ServletContextAttributeEvent scab)
从ServletContext
中移除属性时调用 attributeReplaced(ServletContextAttributeEvent scab)
当ServletContext
中的属性被修改时调用
ServletContextAttributeEvent
对象代表属性变化事件,它包含的方法如下:
方法名 作用 getName()
获取修改或添加的属性名 getValue()
获取被修改或添加的属性值 getServletContext()
获取ServletContext
对象
例如下面的代码:
监听器 ServletA
ServletB
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 @WebListener
public class ApplicationListener implements ServletContextListener , ServletContextAttributeListener {
// 监听初始化
@Override
public void contextInitialized ( ServletContextEvent sce ) {
ServletContext application = sce . getServletContext ();
System . out . println ( "application" + application . hashCode () + " initialized" );
}
// 监听销毁
@Override
public void contextDestroyed ( ServletContextEvent sce ) {
ServletContext application = sce . getServletContext ();
System . out . println ( "application" + application . hashCode () + " destroyed" );
}
// 监听数据增加
@Override
public void attributeAdded ( ServletContextAttributeEvent scae ) {
String name = scae . getName ();
Object value = scae . getValue ();
ServletContext application = scae . getServletContext ();
System . out . println ( "application" + application . hashCode () + " add:" + name + "=" + value );
}
// 监听数据移除
@Override
public void attributeRemoved ( ServletContextAttributeEvent scae ) {
String name = scae . getName ();
Object value = scae . getValue ();
ServletContext application = scae . getServletContext ();
System . out . println ( "application" + application . hashCode () + " remove:" + name + "=" + value );
}
// 监听数据修改
@Override
public void attributeReplaced ( ServletContextAttributeEvent scae ) {
String name = scae . getName ();
Object value = scae . getValue ();
ServletContext application = scae . getServletContext ();
Object newValue = application . getAttribute ( name );
System . out . println ( "application" + application . hashCode () + " change:" + name + "=" + value + " to " + newValue );
}
}
Java // ServletA用于向application域中放入数据
@WebServlet ( urlPatterns = "/servletA" , name = "servletAName" )
public class ServletA extends HttpServlet {
@Override
protected void service ( HttpServletRequest req , HttpServletResponse resp ) throws ServletException , IOException {
// 向application域中放入数据
ServletContext application = this . getServletContext ();
application . setAttribute ( "k1" , "v1" );
application . setAttribute ( "k2" , "v2" );
}
}
Java 1
2
3
4
5
6
7
8
9
10
11
12 // ServletB用于向application域中修改和移除数据
@WebServlet ( urlPatterns = "/servletB" , name = "servletBName" )
public class ServletB extends HttpServlet {
@Override
protected void service ( HttpServletRequest req , HttpServletResponse resp ) throws ServletException , IOException {
ServletContext appliation = getServletContext ();
// 修改application域中的数据
appliation . setAttribute ( "k1" , "value1" );
// 删除application域中的数据
appliation . removeAttribute ( "k2" );
}
}
会话域监听器 HttpSessionListener
:用于监听HttpSession
对象的创建与销毁
方法名 作用 sessionCreated(HttpSessionEvent hse)
HttpSession
对象创建时调用 sessionDestroyed(HttpSessionEvent hse)
HttpSession
对象销毁时调用
其中,HttpSessionEvent
对象代表从HttpSession
对象身上捕获到的事件,通过这个事件对象我们可以获取到触发事件的HttpSession
对象
HttpSessionAttributeListener
:监听HttpSession
中属性的添加、移除和修改
方法名 作用 attributeAdded(HttpSessionBindingEvent se)
向HttpSession
中添加属性时调用 attributeRemoved(HttpSessionBindingEvent se)
从HttpSession
中移除属性时调用 attributeReplaced(HttpSessionBindingEvent se)
当HttpSession
中的属性被修改时调用
其中,HttpSessionBindingEvent
对象代表属性变化事件,它包含的方法如下:
方法名 作用 getName()
获取修改或添加的属性名 getValue()
获取被修改或添加的属性值 getSession()
获取触发事件的HttpSession
对象
例如下面的代码:
监听器 ServletA
ServletB
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 @WebListener
public class SessionListener implements HttpSessionListener , HttpSessionAttributeListener {
// 监听session创建
@Override
public void sessionCreated ( HttpSessionEvent se ) {
HttpSession session = se . getSession ();
System . out . println ( "session" + session . hashCode () + " created" );
}
// 监听session销毁
@Override
public void sessionDestroyed ( HttpSessionEvent se ) {
HttpSession session = se . getSession ();
System . out . println ( "session" + session . hashCode () + " destroyed" );
}
// 监听数据增加
@Override
public void attributeAdded ( HttpSessionBindingEvent se ) {
String name = se . getName ();
Object value = se . getValue ();
HttpSession session = se . getSession ();
System . out . println ( "session" + session . hashCode () + " add:" + name + "=" + value );
}
// 监听数据移除
@Override
public void attributeRemoved ( HttpSessionBindingEvent se ) {
String name = se . getName ();
Object value = se . getValue ();
HttpSession session = se . getSession ();
System . out . println ( "session" + session . hashCode () + " remove:" + name + "=" + value );
}
// 监听数据修改
@Override
public void attributeReplaced ( HttpSessionBindingEvent se ) {
String name = se . getName ();
Object value = se . getValue ();
HttpSession session = se . getSession ();
Object newValue = session . getAttribute ( name );
System . out . println ( "session" + session . hashCode () + " change:" + name + "=" + value + " to " + newValue );
}
}
Java 1
2
3
4
5
6
7
8
9
10
11
12 // servletA用于创建session并向session中放数据
@WebServlet ( urlPatterns = "/servletA" , name = "servletAName" )
public class ServletA extends HttpServlet {
@Override
protected void service ( HttpServletRequest req , HttpServletResponse resp ) throws ServletException , IOException {
// 创建session,并向session中放入数据
HttpSession session = req . getSession ();
session . setAttribute ( "k1" , "v1" );
session . setAttribute ( "k2" , "v2" );
}
}
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14 // servletB用于修改删除session中的数据并手动让session不可用
@WebServlet ( urlPatterns = "/servletB" , name = "servletBName" )
public class ServletB extends HttpServlet {
@Override
protected void service ( HttpServletRequest req , HttpServletResponse resp ) throws ServletException , IOException {
HttpSession session = req . getSession ();
// 修改session域中的数据
session . setAttribute ( "k1" , "value1" );
// 删除session域中的数据
session . removeAttribute ( "k2" );
// 手动让session不可用
session . invalidate ();
}
}
请求域对象 ServletRequestListener
:监听ServletRequest
对象的创建与销毁
方法名 作用 requestInitialized(ServletRequestEvent sre)
ServletRequest
对象创建时调用 requestDestroyed(ServletRequestEvent sre)
ServletRequest
对象销毁时调用
其中,ServletRequestEvent
对象代表从HttpServletRequest
对象身上捕获到的事件,通过这个事件对象可以获取到触发事件的HttpServletRequest
对象。另外还有一个方法可以获取到当前Web应用的ServletContext
对象
ServletRequestAttributeListener
:监听ServletRequest
中属性的添加、移除和修改
方法名 作用 attributeAdded(ServletRequestAttributeEvent srae)
向ServletRequest
中添加属性时调用 attributeRemoved(ServletRequestAttributeEvent srae)
从ServletRequest
中移除属性时调用 attributeReplaced(ServletRequestAttributeEvent srae)
当ServletRequest
中的属性被修改时调用
其中,ServletRequestAttributeEvent
对象代表属性变化事件,它包含的方法如下:
方法名 作用 getName()
获取修改或添加的属性名 getValue()
获取被修改或添加的属性值 getServletRequest ()
获取触发事件的ServletRequest
对象
例如下面的代码:
监听器 ServletA
ServletB
Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 @WebListener
public class RequestListener implements ServletRequestListener , ServletRequestAttributeListener {
// 监听初始化
@Override
public void requestInitialized ( ServletRequestEvent sre ) {
ServletRequest request = sre . getServletRequest ();
System . out . println ( "request" + request . hashCode () + " initialized" );
}
// 监听销毁
@Override
public void requestDestroyed ( ServletRequestEvent sre ) {
ServletRequest request = sre . getServletRequest ();
System . out . println ( "request" + request . hashCode () + " destoryed" );
}
// 监听数据增加
@Override
public void attributeAdded ( ServletRequestAttributeEvent srae ) {
String name = srae . getName ();
Object value = srae . getValue ();
ServletRequest request = srae . getServletRequest ();
System . out . println ( "request" + request . hashCode () + " add:" + name + "=" + value );
}
// 监听数据移除
@Override
public void attributeRemoved ( ServletRequestAttributeEvent srae ) {
String name = srae . getName ();
Object value = srae . getValue ();
ServletRequest request = srae . getServletRequest ();
System . out . println ( "request" + request . hashCode () + " remove:" + name + "=" + value );
}
// 监听数据修改
@Override
public void attributeReplaced ( ServletRequestAttributeEvent srae ) {
String name = srae . getName ();
Object value = srae . getValue ();
ServletRequest request = srae . getServletRequest ();
Object newValue = request . getAttribute ( name );
System . out . println ( "request" + request . hashCode () + " change:" + name + "=" + value + " to " + newValue );
}
}
Java 1
2
3
4
5
6
7
8
9
10
11
12 // servletA向请求域中放数据
@WebServlet ( urlPatterns = "/servletA" , name = "servletAName" )
public class ServletA extends HttpServlet {
@Override
protected void service ( HttpServletRequest req , HttpServletResponse resp ) throws ServletException , IOException {
// 向request中增加数据
req . setAttribute ( "k1" , "v1" );
req . setAttribute ( "k2" , "v2" );
// 请求转发
req . getRequestDispatcher ( "servletB" ). forward ( req , resp );
}
}
Java 1
2
3
4
5
6
7
8
9
10
11
12 // servletB修改删除域中的数据
@WebServlet ( urlPatterns = "/servletB" , name = "servletBName" )
public class ServletB extends HttpServlet {
@Override
protected void service ( HttpServletRequest req , HttpServletResponse resp ) throws ServletException , IOException {
// 修改request域中的数据
req . setAttribute ( "k1" , "value1" );
// 删除session域中的数据
req . removeAttribute ( "k2" );
}
}