学习笔记-拦截器
什么是拦截器
1.官方解释:拦截器(Interceptor)是Struts2框架的核心功能之一,[Struts 2](https://baike.baidu.com/item/Struts 2/2187934)是一个基于MVC设计模式的开源框架, 主要完成请求参数的解析、将页面表单参数赋给值栈中相应属性、执行功能检验、程序异常调试等工作。 java里的拦截器是动态拦截Action调用的对象,它提供了一种机制可以使开发者在一个Action执行的前后执行一段代码,也可以在一个Action执行前阻止其执行,同时也提供了一种可以提取Action中可重用部分代码的方式。在AOP中,拦截器用于在某个方法或者字段被访问之前,进行拦截然后再之前或者之后加入某些操作。
2.简单理解:拦截器将很多service或者Controller中共有的行为提炼出来,在某些方法执行的前后执行,提炼为通用的处理方式,让被拦截的方法都能享受这一共有的功能,让代码更加简洁,同时,当共有的功能需要发生调整、变动的时候,不必修改很多的类或者方法,只要修改这个拦截器就可以了,可复用性很强。
如何实现拦截器
Spring MVC 中的Interceptor拦截请求是通过HandlerInterceptor来实现的。
返回值类型 | 方法声明 | 描述 |
---|---|---|
boolean | preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) | 该方法在控制器处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回 false 表示中断后续操作。 |
void | postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) | 该方法在控制器处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步修改。 |
void | afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) | 该方法在视图渲染结束后执行,可以通过此方法实现资源清理、记录日志信息等工作。 |
SpringBoot中使用拦截器
在 Spring Boot 项目中,使用拦截器功能通常需要以下 3 步:
- 定义拦截器;
- 注册拦截器;
- 指定拦截规则(如果是拦截所有,静态资源也会被拦截)。
浅浅的介绍一下HandlerIntercepter
package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}
代码实现
定义拦截器
package com.tamir.interceptor.interceptor1;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
public class MyInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(MyInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
String methodName = method.getName();
logger.info("==拦截到了:{},在该方法之前执行==",methodName);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
logger.info("执行完方法之后进执行(Controller方法调用之后),但是此时还没进行视图渲染");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
logger.info("整个请求都处理完咯,DispatcherServlet也渲染了对应的视图咯,此时我可以做一些清理的工作了");
}
}
注册拦截器;
@Configuration
public class MyInterceptorConfig extends WebMvcConfigurationSupport {
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/index");
super.addInterceptors(registry);
}
}
解决可能存在的问题,当全局拦截后,静态资源也被拦截可以在 resources/static/ 目录下放置一个图片资源或者 HTML 文件,之后启动项目直接访问,即可看到无法访问的现象。也就是说,虽然 Spring Boot 2.0 废弃了 WebMvcConfigurerAdapter,但是 WebMvcConfigurationSupport 又会导致默认的静态资源被拦截,这就需要我们手动将静态资源放开。
@Configuration
public class MyInterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 实现 WebMvcConfigurer 不会导致静态资源被拦截
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
}
}
通过实现 WebMvcConfigure 接口,使 Spring Boot 默认的静态资源不会拦截。
实现 | 适合 | 原因 |
---|---|---|
继承 WebMvcConfigurationSupport | 前后端分离的项目 | 会拦截静态资源 |
实现接口 WebMvcConfigure | 非前后端分离的项目 | 不会拦截静态资源 |
继承 WebMvcConfigurationSupport 类的方式可以用在前后端分离的项目中,后台不需要访问静态资源(就不需要放开静态资源了);实现 WebMvcConfigure 接口的方式可以用在非前后端分离的项目中,因为需要读取一些图片、CSS、JS 文件等等。
应用场景
拦截器本质上是面向切面编程(AOP),符合横切关注点的功能都可以放在拦截器中来实现,主要的应用场景包括:
- 登录验证,判断用户是否登录。
- 权限验证,判断用户是否有权限访问资源,如校验token
- 日志记录,记录请求操作日志(用户ip,访问时间等),以便统计请求访问量。
- 处理cookie、本地化、国际化、主题等。
- 性能监控,监控请求处理时长等。
使用实例
-
判断用户有没有登录
一般用户的登录功能,我们可以这么实现
第一种:在 Session 中写一个 user
第二种:针对每个 user 生成一个 Token,相比之下,第二种要更好。
第二种方式中,如果用户登录成功,每次请求时都会带上该用户的 Token,如果未登录,则没有该 Token,服务端可以检测这个 Token 参数的有无来判断用
户有没有登录,从而实现拦截功能。我们改造一下 preHandle 方法,如下:
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
String methodName = method.getName();
logger.info("==拦截到了:{},在该方法之前执行==",methodName);
// 判断用户有没有登陆,一般登陆之后的用户都有一个对应的 token
String token = request.getParameter("token");
if (null == token || "".equals(token)) {
logger.info("用户未登录,没有权限执行……请登录");
return false;
}
// 返回 true 才会继续执行,返回 false 则取消当前请求
return true;
}
请求http://localhost:8881/login 不带token结果:
页面无输出;
控制台:
2022-06-13 10:35:34.142 INFO 7144 --- [nio-8881-exec-1] c.t.i.interceptor3.MyInterceptor : ==拦截到了:login,在该方法之前执行==
2022-06-13 10:35:34.143 INFO 7144 --- [nio-8881-exec-1] c.t.i.interceptor3.MyInterceptor : 用户未登录,没有权限执行……请登录
请求http://localhost:8881/login 带token结果:
页面输出:
success
控制台:
2022-06-13 10:36:27.812 INFO 7144 --- [nio-8881-exec-3] c.t.i.interceptor3.MyInterceptor : ==拦截到了:login,在该方法之前执行==
2022-06-13 10:36:27.831 INFO 7144 --- [nio-8881-exec-3] c.t.i.interceptor3.MyInterceptor : 执行完方法之后进执行(Controller方法调用之后),但是此时还没进行视图渲染
2022-06-13 10:36:27.831 INFO 7144 --- [nio-8881-exec-3] c.t.i.interceptor3.MyInterceptor : 整个请求都处理完咯,DispatcherServlet也渲染了对应的视图咯,此时我可以做一些清理的工作了
拦截器执行流程
1、拦截器执行顺序是按照Spring配置文件中定义的顺序而定的。
2、会先按照顺序执行所有拦截器的preHandle方法,一直遇到return false为止,比如第二个preHandle方法是return false,则第三个以及以后所有拦截器都不会执行。若都是return true,则按顺序加载完preHandle方法。
3、然后执行主方法(自己的controller接口),若中间抛出异常,则跟return false效果一致,不会继续执行postHandle,只会倒序执行afterCompletion方法。
4、在主方法执行完业务逻辑(页面还未渲染数据)时,按倒序执行postHandle方法。若第三个拦截器的preHandle方法return false,则会执行第二个和第一个的postHandle方法和afterCompletion(postHandle都执行完才会执行这个,也就是页面渲染完数据后,执行after进行清理工作)方法。(postHandle和afterCompletion都是倒序执行)
post和after是倒序的!!!!