灵魂三问:
是什么? 解决什么问题?如何用?
三大器在springboot中使用时,首先实现相应的接口定义类,然后通过配置类将其加入到spring容器中,从而实现相应的功能。
过滤器和拦截器的区别:
- 拦截器是基于java的反射机制的,而过滤器是基于函数回调;
- 拦截器不依赖于servlet容器,过滤器依赖于servlet容器;
- 拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用;
- 拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问;
- 在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次;
- 拦截器可以获取IOC容器中的各个bean,而过滤器就不行,这点很重要,在拦截器里注入一个service,可以调用业务逻辑。
Spring的拦截器与Servlet的Filter有相似之处,比如二者都是AOP编程思想的体现,都能实现权限检查、日志记录等。不同的是:
- 使用范围不同:Filter是Servlet规范规定的,只能用于Web程序中。而拦截器既可以用于Web程序,也可以用于Application、Swing程序中。
- 规范不同:Filter是在Servlet规范中定义的,是Servlet容器支持的。而拦截器是在Spring容器内的,是Spring框架支持的。
- 使用的资源不同:同其它代码块一样,拦截器也是一个Spring的组件,归Spring管理,配置在Spring文件中,因此能使用Spring里的任何资源、对象,例如service对象、数据源、事务管理等,通过IoC注入到拦截器即可,而Filter则不能。
- 深度不同:Filter只在Servlet前后起作用。而拦截器能够深入到方法前后、异常抛出前后等,因此拦截器的使用具有更大的弹性。所以在Spring架构的程序中,要优先使用拦截器。
触发时机:
过滤器是在请求进入tomcat容器后,进入servlet之前进行预处理的。请求结束返回也是在servlet处理完后,返回给前端之前。
1 过滤器
Filter是Servlet技术中最实用的技术,Web开发人员通过Filter技术,对web服务器管理的所有web资源:例如Jsp, Servlet, 静态图片文件或静态 html 文件等进行拦截,从而实现一些特殊的功能。例如实现URL级别的权限访问控制、过滤敏感词汇、压缩响应信息等一些高级功能。它主要用于对用户请求进行预处理,也可以对HttpServletResponse进行后处理。
使用Filter的完整流程:Filter对用户请求进行预处理,接着将请求交给Servlet进行处理并生成响应,最后Filter再对服务器响应进行后处理。
- 在客户端的请求访问后端资源之前,拦截这些请求。
在服务器的响应发送回客户端之前,处理这些响应。 ```java public interface Filter {
public default void init(FilterConfig filterConfig) throws ServletException {} // 入参是ServletRequest,而不是HttpServletRequest,就是因为过滤器是在httpServlet之前 public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException;
public default void destroy() {} }
> A filter is an object that performs filtering tasks on either the request to a resource (a servlet or static content), or on the response from a resource,or both.
> 过滤器是一个对象,它可以对资源(Servlet或静态内容)的请求或响应执行过滤任务
> Filters perform filtering in the <code>doFilter</code> method. Every Filter has access to a FilterConfig object from which it can obtain its initialization parameters, a reference to the ServletContext which it can use, for example, to load resources needed for filtering tasks.
> filter使用doFilter方法进行过滤。每个过滤器都可以访问FilterConfig对象,它可以从该对象获得其初始化参数,即对ServletContext的引用,例如,它可以使用ServletContext来加载过滤任务所需的资源。
> Filters are configured in the deployment descriptor of a web application
> 过滤器是在web应用程序的部署描述符中配置的。
> Examples that have been identified for this design are
> 1) Authentication Filters 身份验证过滤器
> 2) Logging and Auditing Filters 日志和审计过滤器
> 3) Image conversion Filters 图像转换滤波器
> 4) Data compression Filters 数据压缩过滤器
> 5) Encryption Filters 加密过滤器
> 6) Tokenizing Filters 分过滤器
> 7) Filters that trigger resource access events 触发资源访问事件的过滤器
> 8) XSL/T filters XSL / T过滤器
> 9) Mime-type chain Filter Mime-type过滤器
<a name="7e65fc3fb085f9425f836fb54a525684"></a>
## 1.1 init方法
**public default void init(FilterConfig filterConfig) throws ServletException {}**<br />**FilterConfig filterConfig // 与正在初始化的过滤器实例相关联的配置信息**
> Called by the web container to indicate to a filter that it is being placed into service. The servlet container calls the init method exactly once after instantiating the filter. The init method must complete successfully before the filter is asked to do any filtering work.
> 由web容器调用,以指示过滤器它正在被放置到服务中。servlet容器在实例化过滤器之后只调用init方法一次。init方法必须在过滤器被要求做任何过滤工作之前成功完成。
> The web container cannot place the filter into service if the init method either Throws a ServletException or Does not return within a time period defined by the web container.
> 如果init方法抛出一个ServletException,或者在web容器定义的时间段内没有返回,web容器就不能将过滤器放入服务中。
<a name="f14af2dfce6e8e1a8f85c37aa4c5b530"></a>
## 1.2 doFilter方法
filter的生命周期和servlet非常相同,不同的地方是,doFilter方法会在拦截到servlet前执行,并在servlet执行后再次执行,分割是chain.doFilter(request, response);,<br />**public void doFilter(ServletRequest request,ServletResponse response, FilterChain chain ) throws IOException, ServletException;**<br />**ServletRequest request, // 要处理的请求**<br />**ServletResponse response, // 与请求相关联的响应**<br />**FilterChain chain // 提供对链中的下一个过滤器的访问,以便此过滤器将请求和响应传递给该过滤器以进行进一步处理**
> The doFilter method of the Filter is called by the container each time a request/response pair is passed through the chain due to a client request for a resource at the end of the chain. The FilterChain passed in to this method allows the Filter to pass on the request and response to the next entity in the chain.
> 由于客户端请求链末端的资源,每次请求/响应对通过链传递时,容器都会调用过滤器的doFilter方法。传递给此方法的FilterChain允许过滤器将请求和响应传递给链中的下一个实体。
> A typical implementation of this method would follow the following pattern:
> 1. Examine the request
> 2. Optionally wrap the request object with a custom implementation to filter content or headers for input filtering
> 3. Optionally wrap the response object with a custom implementation to filter content or headers for output filtering
> 4.
> a) <strong>Either</strong> invoke the next entity in the chain using the FilterChain object (<code>chain.doFilter()</code>),
> b) <strong>or</strong> not pass on the request/response pair to the next entity in the filter chain to block the request processing
> 5. Directly set headers on the response after invocation of the next entity in the filter chain.
> 该方法的典型实现将遵循以下模式:
> 1. 检查请求
> 2. 可以选择使用自定义实现包装请求对象,以过滤用于输入过滤的内容或头
> 3. 可以选择使用自定义实现包装响应对象,以过滤输出过滤的内容或头
> 4.
> a) 使用FilterChain对象chain. dofilter ()调用链中的下一个实体
> b) 不将请求/响应对传递给过滤器链中的下一个实体以阻止请求处理
> 5. 在调用过滤器链中的下一个实体后,直接在响应上设置请求头。
<a name="7abfa70f12355d3bc4f5a28ea9361b31"></a>
## 1.3 destroy方法
**public default void destroy() {}**
> Called by the web container to indicate to a filter that it is being taken out of service. This method is only called once all threads within the filter's doFilter method have exited or after a timeout period has passed. After the web container calls this method, it will not call the doFilter method again on this instance of the filter.
> This method gives the filter an opportunity to clean up any resources that are being held (for example, memory, file handles, threads) and make sure that any persistent state is synchronized with the filter's current state in memory.
> 由web容器调用,以指示该过滤器将退出服务。这个方法只在过滤器的doFilter方法中的所有线程退出或超时之后才被调用。在web容器调用这个方法之后,它不会对这个过滤器实例再次调用doFilter方法。
> 这个方法让过滤器有机会清理所占用的任何资源(例如,内存、文件句柄、线程),并确保任何持久状态与过滤器在内存中的当前状态是同步的。
<a name="f124376fdcbac6327a2050d15cd334b7"></a>
## 1.4 案例
**注意:**<br />@WebFilter不能和@Component共用,如果添加了@Component或@Configuration,又添加了@WebFilter,那么会初始化两次Filter,并且会过滤所有路径+自己指定的路径 ,便会出现对没有指定的URL也会进行过滤的情况。要使得过滤器生效,需要在在SpringBootApplication(启动类)上使用@ServletComponentScan注解。这样的话Servlet、Filter(过滤器)、Listener(监听器)可以直接通过@WebServlet、@WebFilter、@WebListener注解自动注册,无需其他代码!<br />【案例1】获取请求参数,将参数中可能存在xss攻击的字符串进行转义。<br />获取请求参数,第一个想到的就是使用request.getParameter(参数名)的方法,但是这个方法只能在get请求中取到参数,如果获取post请求的参数,虽然不会报错,但是参数对应的值永远都是null。<br />使用流的方式如何呢?调用request.getInputStream()获取流,然后从流中读取参数,貌似可以哦!<br />**方式一:使用流的方式获取参数:**
```java
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String body = "";
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
InputStream inputStream = null;
try {
inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
stringBuilder.append("");
}
} catch (IOException ex) {
ex.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedReader != null) {
try {
bufferedReader.close();
}
catch (IOException e) {
e.printStackTrace();
}
}
}
body = stringBuilder.toString();
System.out.println("请求参数"+body);
// 开始执行Controller逻辑,如果有拦截器的话,在执行Controller前会先执行拦截器
chain.doFilter(request,response);
}
代码中的body就是request中的参数,我这里传的是JSON数据:
{
“content”: {
“html”: “
“json”: “
},
“labelId”: 0,
“subtitleList”: [
{
“html”: “
“json”: “
}
],
“title”: {
“html”: “
“json”: “
}
},
那么body就是:body = “json中的内容”,一个JSON字符串。这样是可以成功获取到post请求的body,但是,经过过滤器后,参数经过@RequestBody注解赋值给controller中的方法的时候,request中的值却为空。
为什么request中的值为空?
如何是好?
重写HttpServletRequestWrapper把request保存下来,然后通过过滤器把保存下来的request再填充进去,这样就可以多次读取request了。
https://blog.csdn.net/SmallTenMr/article/details/82786468?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&dist_request_id=&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control
【案例2】用户登录鉴权
利用token,用户登录成功后,将生成的token保存在redis中,并将token返回给前端,用户在以后的请求中都在request的header中携带着token。
过滤器不拦截(过滤)登录接口,只要根据用户名和登录密码在数据库中查询到即证明登陆成功,登录成功后,将用户的一些常用的数据存储在缓存中,当用户带着token请求其它接口时就可以直接在缓存中获取常用用户数据。
定义一个传给前端的VO ```java @Data public class UserBean {
@ApiModelProperty(value = “用户id”) private Integer userId;
@ApiModelProperty(value = “用户名”) private String userName;
@ApiModelProperty(value = “用户token”) private String token;
}
用户登录Bean
```java
public class UserLogin extends BaseEntity implements Serializable {
private static final long serialVersionUID = -3262228368824964713L;
@TableId
@IsAutoIncrement
@IsKey
@Column(comment = "主键ID", length = 10, type = MySqlTypeConstant.INT)
@ApiModelProperty(value = "主键ID")
private Integer id;
@Column(name = "user_id", comment = "用户id", length = 10, type= MySqlTypeConstant.INT)
private Integer userId;
@Column(name = "token", comment = "授权码", length = 255, type= MySqlTypeConstant.VARCHAR)
private String token;
@Column(name = "last_login_datetime", comment = "最后一次登录时间", type= MySqlTypeConstant.DATETIME)
private LocalDateTime lastLoginDatetime;
}
登录服务
public UserBean login (LoginBean user) {
// ================ 数据操作 =================
// 查询用户是否在数据表中
UserLogin userLogin = userLoginMapper.selectByUser(user.getUserId());
if (userLogin==null){
throw new BusinessException("登录失败");
}
// 用户成功登录时生成token
userLogin.setToken(UUID.randomUUID().toString());
// 记录用户的登录时间
userLogin.setLastLoginDatetime(DateUtil.current());
// 更新用户的登录时间
userLoginMapper.updateById(userLogin);
// ================== redis操作 ==============
/**
* USER_ID = "user-id:";
* 用户token过期时间 单位s 90天
* TOKEN_TIME_TO_LIVE = 90 * 24 * 60 * 60;
*/
// 1.保存当前用户的token至缓存 key为userId
String keyU = StringUtils.appendToStr(RedisPrefix.USER_ID,user.getUserId());
if(redissionUtils.isExist(keyU)){
// 如果redis中存在当前用户的key,就更新key对应的token
redissionUtils.getRBucket(keyU).set(userLogin.getToken(), RedisPrefix.TOKEN_TIME_TO_LIVE, TimeUnit.SECONDS);
}else{
// 如果不存在就保存当前用户的token
redissionUtils.setRBucket(keyU, userLogin.getToken(), RedisPrefix.TOKEN_TIME_TO_LIVE, TimeUnit.SECONDS);
}
// 2.保存用户信息对象至缓存 key为token
// 获取用户权限数据的id
List<Integer> permissionIds = userDataRoleMapper.getPermissionIds(user.getUserId(), 2);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < permissionIds.size(); i++) {
sb.append(permissionIds.get(i));
if (i!=permissionIds.size()-1){
sb.append(',');
}
}
user.setPlacesPermission(sb.toString());
String keyT = StringUtils.appendToStr(RedisPrefix.USER_TOKEN,userLogin.getToken());
redissionUtils.setRBucket(keyT, user, RedisPrefix.TOKEN_TIME_TO_LIVE, TimeUnit.SECONDS);
//set返回给前端的数据
UserBean userBean = new UserBean();
if (user.getUserId() != null) {
userBean.setUserId(user.getUserId());
}
if (!StringUtils.isEmpty(user.getUserName())) {
userBean.setUserName(user.getUserName());
}
if (user.getPlaceId() != null) {
userBean.setPlaceId(user.getPlaceId());
}
if (user.getRoleId() != null) {
userBean.setRoleId(user.getRoleId());
}
if (!StringUtils.isEmpty(user.getMenuData())) {
userBean.setMenuData(user.getMenuData());
}
if (!StringUtils.isEmpty(user.getPlacesPermission())) {
userBean.setPlacesPermission(user.getPlacesPermission());
}
if (!StringUtils.isEmpty(userLogin.getToken())) {
userBean.setToken(userLogin.getToken());
}
return userBean;
}
写过滤器 ```java @Component @Slf4j public class WebFilter extends OncePerRequestFilter {
@Override protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
//获取token
String token = httpServletRequest.getHeader("token");
if(StringUtils.isEmpty(token)){
throw new BusinessException("当前用户登录失效 请重新登录");
}
// 判断当前前端传来的token是否和redis中存储的一致
String tokenKey = StringUtils.appendToStr(RedisPrefix.USER_TOKEN,token);
try {
if(redissionUtils.isExist (tokenKey)) {
// 获取用户
LoginBean loginBean = (LoginBean)redissionUtils.getRBucket(tokenKey).get();
String userKey = StringUtils.appendToStr(RedisPrefix.USER_ID, loginBean.getUserId());
if (redissionUtils.isExist (userKey)) {
String realToken = (String)redissionUtils.getRBucket(userKey).get();
if (token.equals (realToken)) {
//直接将获取的值 赋值给 header
RequestHeader header = RequestHeader.builder()
.userId(loginBean.getUserId())
.placeId(loginBean.getPlaceId())
.roleId(loginBean.getRoleId())
.userName(loginBean.getUserName())
.placesPermission(loginBean.getPlacesPermission())
.build();
String source = URLEncoder.encode(JSONObject.toJSONString(header), "utf-8");
//先SET
httpServletRequest.setAttribute("reqHeader", source);
String req = (String) httpServletRequest.getAttribute("reqHeader");
}
}
<a name="63638019eeacb2e44ac9ccc6f0cc77d2"></a>
# 2 拦截器
Interceptor 在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。比如日志,安全等。一般拦截器方法都是通过动态代理的方式实现。可以通过它来进行权限验证,或者判断用户是否登陆,或者是像12306 判断当前时间是否是购票时间。<br />**在Spring MVC中定义一个拦截器有两种方法**:<br />实现HandlerInterceptor接口<br />实现WebRequestInterceptor接口
<a name="IG21f"></a>
## 2.1实现HandlerInterceptor接口
该接口位于org.springframework.web.servlet的包中,定义了三个方法,若要实现该接口,就要实现其三个方法:
- **preHandle()**方法:在控制器方法之前执行,返回true表示继续执行,false表示拦截请求,不再向下执行。
- **postHandle()**方法:在控制器方法之后,返回ModelAndView之前执行。
- **afterCompletion()**方法:在执行完控制器之后执行。
<a name="niRlW"></a>
## 2.2 实现WebRequestInterceptor接口
WebRequestInterceptor中也定义了三个方法,也是通过这三个方法来实现拦截的。<br />**这三个方法都传递了同一个参数WebRequest**, WebRequest 是Spring 定义的一个接口,它里面的方法定义都基本跟HttpServletRequest 一样,**在WebRequestInterceptor 中对WebRequest 进行的所有操作都将同步到HttpServletRequest 中,然后在当前请求中一直传递**。<br />三个方法如下:
- **preHandle(WebRequest request):** 返回值为void,在请求处理之前调用。可以使用WebRequest的setAttribute(name, value, scope)方法把参数放到WebRequest属性中。
scope是Integer类型,在WebRequest的父层接口RequestAttributes中定义了三个常量:
- SCOPE_REQUEST:值为0,表示只有在request中可以访问
- SCOPE_SESSION:值为1,环境允许的情况下,表示一个隔离的session,普通情况下代表一个普通session,并且在该session范围内可以访问。
- SCOPE_GLOBAL_SESSION:值为2,环境允许的情况下,表示一个全局共享的session,普通情况下代表一个普通session,并且在该session范围内可以访问。
- **postHandle(WebRequest request, ModelMap model):** 在Controller方法之后,在视图返回被渲染之前调用,通过改变ModelMap来改变数据的展示。
- **afterCompletion(WebRequest request, Exception ex) :** 在视图返回后并被渲染之后执行,如果在Controller中抛出异常,可以在此处理。如果已经被Spring异常处理器处理了,此处为null。
<a name="hSvHS"></a>
## 2.3 案例【用户登录鉴权】
用户登录,登陆**成功后**服务端会给前端一个token,并把用户信息存储到redis中,用户再次请求接口的时候就会携带着这个token,在请求到达controller之前要拦截(拦截器的作用时机)用户的请求,校验用户的token,如果前端传来的token和服务端存储的token一致就说明用户合法,然后就将此token对应的用户的信息从redis中获取,然后存储到当前请求的线程中,此用户在token有效期内的所有请求都可以直接使用存储在当前请求线程中的所有用户信息。<br />技术识别:
<a name="dc20506c7037d511ce6bc3134df33324"></a>
# 3 监听器
springMVC监听器主要的作用就是spring容器启动的时候加载一些数据,最常用的功能就是开发权限系统的时候,当监听器启动的时候,从数据库加载权限url.<br />listener是servlet规范中定义的一种特殊类。用于监听servletContext、HttpSession和servletRequest等域对象的创建和销毁事件。监听域对象的属性发生修改的事件。用于在事件发生前、发生后做一些必要的处理。其主要可用于以下方面:
1. 统计在线人数和在线用户
1. 系统启动时加载初始化信息
1. 统计网站访问量
1. 记录用户访问路径。
<a name="d2c69a0492357ef4566c143269feacfb"></a>
## 3.1 监听器分类
1. **监听域对象自身创建和销毁的监听器:**
1. ServletContextListener接口 监听 SercvletContext对象
1. ServletRequestListener接口 监听 ServletRequest对象
1. HttpSessionListener接口 监听 HttpSession对象
2. **监听域对象中的属性的增加、修改和删除的事件监听器:**
1. ServletContextAttributeListener接口 监听 SercvletContext对象属性
1. ServletRequestAttributeListener接口 监听 ServletRequest对象属性
1. HttpSessionAttributeListener接口 监听 HttpSession对象属性
3. **监听绑定到 HttpSession 域中某个对象的状态的事件监听器:**
1. HttpSessionBindingListener接口 监听 实现了HttpSessionBindingListener接口的对象的session绑定和解除
1. HttpSessionActivationListener接口 (实现会话的持久化)
<a name="a0efcf3c3ed0999d6272557715f1a7b8"></a>
### 3.1.1 监听域对象自身创建和销毁的监听器
<a name="a9fbb1a8a083820ac4cb8461fa77ef7a"></a>
#### 3.1.1.1 ServletContextListener
**该监听器的作用**:<br />它能够监听 ServletContext 对象的生命周期,实际上就是监听 Web 应用的生命周期。对相关资源进行初始化工作,如创建数据库连接池、创建Spring IOC 容器、读取当前Web应用的初始化参数等。
```java
/**
* 通知web应用程序初始化进程正在启动。
* 在web应用程序中的任何过滤器或servlet初始化之前,都会通知所有servletcontextlistener上下文初始化。
*/
public interface ServletContextListener extends EventListener {
/**
* 通知web应用程序初始化进程正在启动。
* 在web应用程序中的任何过滤器或servlet初始化之前,都会通知所有servletcontextlistener上下文初始化
* @param sce Information about the ServletContext that was initialized 关于已初始化的ServletContext的信息
*/
public default void contextInitialized(ServletContextEvent sce) {}
// 通知servlet上下文即将被关闭。
// 在任何servletcontextlistener被通知上下文销毁之前,所有servlet和过滤器都已销毁。
public default void contextDestroyed(ServletContextEvent sce) {}
}
参数ServletContextEvent(监听器事件对象)
ServletContextEvent: 是ServletContext域对象的事件对象, 此对象由tomcat引擎创建ServletContext。
- 方法
- Object getSource() 获取到被监听的事件源
- ServletContext getServletContext() 获取到被监听的事件源
小结
- 两个方法除了返回值外,功能实现是一致的,这样设计的目的是为了通用性
- 其他的监听器事件对象(HttpSessionEvent, ServletRequestEvent), 都有共同的方法 getSource() ```java @WebListener public class MyServletContextListener implements ServletContextListener {
@Override public void contextInitialized(ServletContextEvent sce) { System.out.println(“ServletContext域对象创建”); ServletContext context = (ServletContext)sce.getSource(); System.out.println(context);
ServletContext servletContext = sce.getServletContext(); System.out.println(servletContext); } @Override public void contextDestroyed(ServletContextEvent sce) { System.out.println(“ServletContext域对象销毁”); } } ```
结果
ServletContext域对象创建
org.apache.catalina.core.ApplicationContextFacade@7d61eccf
org.apache.catalina.core.ApplicationContextFacade@7d61eccf
2021-04-12 11:16:15.624 INFO 27328 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2021-04-12 11:16:15.793 INFO 27328 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-04-12 11:16:15.800 INFO 27328 --- [ main] com.example.demo.ExcelApplication : Started ExcelApplication in 1.461 seconds (JVM running for 2.141)
Disconnected from the target VM, address: '127.0.0.1:3173', transport: 'socket'
ServletContext域对象销毁
2021-04-12 11:19:58.275 INFO 27328 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'
注意:
- Servlet3.0中的监听器跟之前2.5的差别不大,唯一的区别就是增加了对注解的支持。
- 在SpringBoot中使用该注解。@WebListener注解的对象也需要装配。
- 方法1.为你的SpringBootApplication添加@ServletComponentScan注解
- 方法2.在你的SpringBootApplication中添加如下方法为你的Listener装配
【案例】在服务启动时,将数据库中的数据加载进内存,并将其赋值给一个属性名,其它的 Servlet 就可以通过 getAttribute 进行属性值的访问。
【实现】
- ServletContext 对象是一个为整个 web 应用提供共享的内存,任何请求都可以访问里面的内容。
- 步骤:
- 实现 servletContextListerner 接口 并将要共享的通过 setAttribute ( name,data )方法提交到内存中去;
- 项目中可以通过 getAttribute(name) 取到数据 。
3.1.1.2 ServletRequestListener
创建和销毁时机:
每次请求就会创建一次和销毁一次,即每次刷新页面都会导致Request的创建和销毁
转发是一次响应,可以获取Request的信息;
重定向是两次响应,即前一个页面的Request对象和重定向后的页面的Request对象不是同一个对象,因此不能获得前一个对象的Request信息。
该监听器的作用:
读取参数,记录访问历史等
/**
* Implementations of this interface receive notifications about changes to the servlet context of the web application they are part of. To receive notification events,
* the implementation class must be configured in the deployment descriptor for the web application.
* 这个接口的实现接收关于它们所属的web应用程序的servlet上下文更改的通知。要接收通知事件,必须在web应用程序的部署描述符中配置实现类。
*/
public interface ServletRequestListener extends EventListener {
// 请求将进入web应用程序的范围。
public default void requestInitialized (ServletRequestEvent sre) {}
// 请求即将超出web应用程序的范围。
public default void requestDestroyed (ServletRequestEvent sre) {}
}
3.1.1.3 HttpSessionListener
Session什么时候销毁?
关闭服务器、Session过期、手动调用session.invalidate()方法
注意:
用户关闭浏览器时原有Session并不会销毁,会等到timeout超时自动销毁
该监听器的作用:
统计在线人数、记录访问日志等
/**
* Implementations of this interface are notified of changes to the list of active sessions in a web application. To receive notification events, the implementation class
* must be configured in the deployment descriptor for the web application.
* 当web应用程序中的活动会话列表发生更改时,将通知此接口的实现。要接收通知事件,必须在web应用程序的部署描述符中配置实现类。
*/
public interface HttpSessionListener extends EventListener {
// 会话已创建的通知。
public default void sessionCreated(HttpSessionEvent se) {}
// 会话即将失效的通知。
public default void sessionDestroyed(HttpSessionEvent se) {}
}
3.1.2 监听域对象中的属性的增加、修改和删除的事件监听器
3.1.2.1 ServletContextAttributeListener
/**
* Implementations of this interface receive notifications of changes to the attribute list on the servlet context of a web application. To receive
* notification events, the implementation class must be configured in the deployment descriptor for the web application.
*
* 这个接口的实现接收web应用程序的servlet上下文上属性列表更改的通知。要接收通知事件,必须在web应用程序的部署描述符中配置实现类。
*/
public interface ServletContextAttributeListener extends EventListener {
/**
* Notification that a new attribute was added to the servlet context.Called after the attribute is added.The default implementation is a NO-OP.
* 通知一个新属性被添加到servlet上下文。添加属性后调用。默认的实现是NO-OP。
* @param scae Information about the new attribute 关于新属性的信息
*/
public default void attributeAdded(ServletContextAttributeEvent scae) {}
/**
* Notification that an existing attribute has been removed from the servlet context. Called after the attribute is removed.The default implementation is a NO-OP.
* 通知现有属性已从servlet上下文中删除。移除属性后调用。默认的实现是NO-OP。
* @param scae Information about the removed attribute 关于被删除属性的信息
*/
public default void attributeRemoved(ServletContextAttributeEvent scae) {}
/**
* Notification that an attribute on the servlet context has been replaced.Called after the attribute is replaced.The default implementation is a NO-OP.
* 通知servlet上下文上的一个属性已被替换。属性被替换后调用。默认的实现是NO-OP。
* @param scae Information about the replaced attribute 被替换属性的信息
*/
public default void attributeReplaced(ServletContextAttributeEvent scae) {}
}
【案例】
attributeAdded(ServletContextAttributeEvent arg0): request.getServletContext().setAttribute("name","value")初次创建调用
attributeReplaced(ServletContextAttributeEvent arg0): request.getServletContext().setAttribute("name","newValue")被修改时调用
attributeRemoved(ServletContextAttributeEvent arg0): 执行request.getServletContext().removeAttribute("name")时调用
以上三个方法中可用arg0.getName()获取属性名arg0.getValue()获取属性值,但是要注意在attributeReplaced()中获取的是旧的值
3.1.2.2 ServletRequestAttributeListener
/**
* A ServletRequestAttributeListener can be implemented by the developer interested in being notified of request attribute changes.
* Notifications will be generated while the request is within the scope of the web application in which the listener is registered.
* A request is defined as coming into scope when it is about to enter the first servlet or filter in each web application,
* as going out of scope when it exits the last servlet or the first filter in the chain.
* 对请求属性更改通知感兴趣的开发人员可以实现ServletRequestAttributeListener。
* 当请求在注册监听器的web应用程序范围内时,将生成通知。
* 请求在进入每个web应用程序中的第一个servlet或过滤器时被定义为进入范围,在退出链中的最后一个servlet或第一个过滤器时被定义为超出范围。
*/
public interface ServletRequestAttributeListener extends EventListener {
/**
* Notification that a new attribute was added to the servlet request. Called after the attribute is added.
* 通知一个新属性被添加到servlet请求中。添加属性后调用。默认的实现是NO-OP。
* @param srae Information about the new request attribute 关于新请求属性的信息
*/
public default void attributeAdded(ServletRequestAttributeEvent srae) {}
/**
* Notification that an existing attribute has been removed from the servlet request. Called after the attribute is removed.
* 通知现有属性已从servlet请求中删除。移除属性后调用。
* @param srae Information about the removed request attribute 关于已删除请求属性的信息
*/
public default void attributeRemoved(ServletRequestAttributeEvent srae) {}
/**
* Notification that an attribute was replaced on the servlet request. Called after the attribute is replaced.
* 通知servlet请求上的属性被替换。属性被替换后调用。
* @param srae Information about the replaced request attribute 关于已替换请求属性的信息
*/
public default void attributeReplaced(ServletRequestAttributeEvent srae) {}
}
【案例】
attributeAdded(ServletRequestAttributeEvent arg0): request.setAttribute("name", "requestValue")初次创建调用
attributeReplaced(ServletRequestAttributeEvent arg0): request.setAttribute("name", "newRequestValue")被修改时调用
attributeRemoved(ServletRequestAttributeEvent arg0): 执行request.removeAttribute("name")时调用
以上三个方法中可用arg0.getName()获取属性名arg0.getValue()获取属性值,但是要注意在attributeReplaced()中获取的是旧的值
3.1.2.3 HttpSessionAttributeListener
/**
* This listener interface can be implemented in order to get notifications of changes to the attribute lists of sessions within this web application.
* 可以实现这个监听器接口,以便获得对这个web应用程序中会话的属性列表的更改通知。
*/
public interface HttpSessionAttributeListener extends EventListener {
/**
* Notification that an attribute has been added to a session. Called after the attribute is added.
* 属性已添加到会话的通知。添加属性后调用。
* @param se Information about the added attribute 添加的属性信息
*/
public default void attributeAdded(HttpSessionBindingEvent se) {}
/**
* Notification that an attribute has been removed from a session. Called after the attribute is removed.
* 从会话中删除属性的通知。移除属性后调用。
* @param se Information about the removed attribute 关于被删除属性的信息
*/
public default void attributeRemoved(HttpSessionBindingEvent se) {}
/**
* Notification that an attribute has been replaced in a session. Called after the attribute is replaced.
* 通知某个属性在会话中已被替换。属性被替换后调用。
* @param se Information about the replaced attribute 被替换属性的信息
*/
public default void attributeReplaced(HttpSessionBindingEvent se) {}
}
【案例】
attributeAdded(HttpSessionBindingEvent arg0): session.setAttribute("name", "sessionValue")初次创建调用
attributeReplaced(HttpSessionBindingEvent arg0): session.setAttribute("name", "newSessionValue")被修改时调用
attributeRemoved(HttpSessionBindingEvent arg0): 执行session.removeAttribute("name")时调用
以上三个方法中可用arg0.getName()获取属性名arg0.getValue()获取属性值,但是要注意在attributeReplaced()中获取的是旧的值
3.1.3 监听绑定到 HttpSession 域中某个对象的状态的事件监听器
3.1.3.1 HttpSessionBindingListener
/**
* Causes an object to be notified when it is bound to or unbound from a session. The object is notified by an {@link HttpSessionBindingEvent} object.
* This may be as a result of a servlet programmer explicitly unbinding an attribute from a session, due to a session being invalidated, or due to a session timing out.
* 将对象绑定到会话或解除绑定时,将通知该对象。该对象由{@link httpessionbindingevent}对象通知。
* 这可能是由于servlet程序员显式地将属性与会话解除绑定,也可能是由于会话无效,或者由于会话超时。
* @see HttpSession
* @see HttpSessionBindingEvent
*/
public interface HttpSessionBindingListener extends EventListener {
/**
* Notifies the object that it is being bound to a session and identifies the session.
* 通知对象它被绑定到一个会话,并标识该会话。
* @param event the event that identifies the session 标识会话的事件
*/
public default void valueBound(HttpSessionBindingEvent event) {
}
/**
* Notifies the object that it is being unbound from a session and identifies the session.
* 通知对象它正在与会话解除绑定,并标识会话。
* @param event the event that identifies the session 标识会话的事件
*/
public default void valueUnbound(HttpSessionBindingEvent event) {
}
}
【案例】
前提:javaBean实现了该接口
相关方法
valueBound(HttpSessionBindingEvent arg0): session.setAttribute("name", javaBean)触发绑定方法
valueUnbound(HttpSessionBindingEvent arg0): session.removeAttribute("name")触发绑定解除方法
3.1.3.2 HttpSessionActivationListener
/**
* Objects that are bound to a session may listen to container events notifying them that sessions will be passivated and that session will be activated.
* A container that migrates session between VMs or persists sessions is required to notify all attributes bound to sessions implementing HttpSessionActivationListener.
* 绑定到会话的对象可以监听容器事件,通知它们会话将被钝化,会话将被激活。
* 需要一个在虚拟机之间迁移会话或持久化会话的容器来通知绑定到实现httpessionactivationlistener的会话的所有属性。
*/
public interface HttpSessionActivationListener extends EventListener {
/**
* Notification that the session is about to be passivated.
* 会话即将被钝化的通知。
* @param se Information about the session this is about to be passivated 关于将要被钝化的会话的信息
*/
public default void sessionWillPassivate(HttpSessionEvent se) {
}
/**
* Notification that the session has just been activated.
* 通知会话刚刚被激活。
* @param se Information about the session this has just been activated 关于刚刚被激活的会话的信息
*/
public default void sessionDidActivate(HttpSessionEvent se) {
}
}
【案例】
实现会话的持久化
前提:实现该接口和序列化接口Serializable
可以感知自己被活化(从硬盘到内存)和钝化(从内存到硬盘)的过程
当服务器突然关闭,用户的session就不存在了,即用户就需要重新登录等操作,这样很麻烦,于是我们就需要实现会话的持久化来解决。
可以让我们在重新启动服务器之后用户的session还在服务器中存在
相关方法
sessionWillPassivate(HttpSessionEvent arg0): 钝化方法,关闭服务器调用的方法
可以将用户的Session储存到tomcat目录下的/work/Catalina/localhost/项目名 下的SESSION.ser文件中
sessionDidActivate(HttpSessionEvent arg0): 活化方法,重新启动服务器时调用
Session从硬盘回复到内存中,目录下的SESSION.ser文件小消失