文章目录
拦截器
拦截器
1. 拦截器的概念和作用
1.1 什么是拦截器
拦截器(Interceptor)是一种特殊的组件,它可以在请求处理的过程中对请求和响应进行拦截和处理。拦截器可以在请求到达目标处理器之前、处理器处理请求之后以及视图渲染之前执行特定的操作。拦截器的主要目的是在不修改原有代码的情况下,实现对请求和响应的统一处理。
1.2 拦截器的作用
拦截器可以用于实现以下功能:
权限控制:拦截器可以在请求到达处理器之前进行权限验证,从而实现对不同用户的访问控制。
日志记录:拦截器可以在请求处理过程中记录请求和响应的详细信息,便于后期分析和调试。
接口幂等性校验:拦截器可以在请求到达处理器之前进行幂等性校验,防止重复提交。
数据校验:拦截器可以在请求到达处理器之前对请求数据进行校验,确保数据的合法性。
缓存处理:拦截器可以在请求处理之后对响应数据进行缓存,提高系统性能。
1.3 拦截器与过滤器的区别
拦截器和过滤器都可以实现对请求和响应的拦截和处理,但它们之间存在以下区别:
执行顺序:过滤器在拦截器之前执行,拦截器在处理器之前执行。
功能范围:过滤器可以对所有请求进行拦截,而拦截器只能对特定的请求进行拦截。
生命周期:过滤器由Servlet容器管理,拦截器由Spring容器管理。
使用场景:过滤器适用于对请求和响应的全局处理,拦截器适用于对特定请求的处理。
2. springboot拦截器的实现
在 Spring Boot 项目中,使用拦截器功能通常需要以下 3 步:
-
定义拦截器;
-
注册拦截器;
-
指定拦截规则(如果是拦截所有,静态资源也会被拦截)
2.1 定义拦截器
返回值类型 | 方法声明 | 描述 | |
---|---|---|---|
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) | 该方法在视图渲染结束后执行,可以通过此方法实现资源清理、记录日志信息等工作。 |
实现HandlerInterceptor接口
public class MyFirstInteceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle"+request.getRequestURI());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle"+request.getRequestURI());
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion"+request.getRequestURI());
}
}
2.2 注册拦截器到InterceptorRegistry
要让拦截器生效,需要将其注册到InterceptorRegistry中。这可以通过实现WebMvcConfigurer接口并重写addInterceptors方法来实现。以下是一个简单的注册示例:
WebMvcConfigurationSupport
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor());
}
}
2.3 配置拦截器的拦截规则
在注册拦截器时,可以通过addPathPatterns和excludePathPatterns方法来配置拦截器的拦截规则。addPathPatterns方法用于指定需要拦截的请求路径,excludePathPatterns方法用于指定不需要拦截的请求路径。以下是一个配置示例:
public class WebMvcConfig extends WebMvcConfigurationSupport {
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyFirstInteceptor())
.addPathPatterns("/**")
.excludePathPatterns("/transcational/run");
}
}
2.4 参数解读
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String header = request.getHeader("Content-Length");
System.out.println("preHandle header"+ header);
System.out.println("preHandle handler"+handler);
HandlerMethod hm = (HandlerMethod)handler;
System.out.println("preHandle handler mentod_name" + hm.getMethod().getName());
return true;
}
其中 request.getHeader 是读取 请求头中的信息
其中 Object Handler , 是被调用的处理器对象,本质上是一个方法对象,对反射技术中的Method对象进行了再包装
返回值
preHandle header0
preHandle handlerrejjie.Intecptor.IntecptorController#testController()
preHandle handler mentod_nametestController
控制器内部的方法开始运行
postHandle/inteceptor/test
afterCompletion/inteceptor/test
3. 拦截器的进一步 理解 和 执行顺序
HandlerInterceptor是springMVC项目中的拦截器,它拦截的目标是请求的地址,比MethodInterceptor先执行。实现一个HandlerInterceptor拦截器可以直接实现HandlerInterceptor接口,也可以继承HandlerInterceptorAdapter类。这两种方法殊途同归,其实HandlerInterceptorAdapter也就是声明了HandlerInterceptor接口中所有方法的默认实现,而我们在继承它之后只需要重写必要的方法。下面就是HandlerInterceptorAdapter的代码,可以看到一个方法只是默认返回true,另外两个是空方法:
public abstract class HandlerInterceptorAdapter implements HandlerInterceptor {
/**
* This implementation always returns <code>true</code>.
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}
/**
* This implementation is empty.
*/
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
}
/**
* This implementation is empty.
*/
public void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
}
代码有点长,但是它封装了springMVC处理请求的整个过程。首先根据请求找到对应的HandlerExecutionChain,它包含了处理请求的handler和所有的HandlerInterceptor拦截器;然后在调用hander之前分别调用每个HandlerInterceptor拦截器的preHandle方法,若有一个拦截器返回false,则会调用triggerAfterCompletion方法,并且立即返回不再往下执行;若所有的拦截器全部返回true并且没有出现异常,则调用handler返回ModelAndView对象;再然后分别调用每个拦截器的postHandle方法;最后,即使是之前的步骤抛出了异常,也会执行triggerAfterCompletion方法。关于拦截器的处理到此为止,接下来看看triggerAfterCompletion做了什么
private void triggerAfterCompletion(HandlerExecutionChain mappedHandler,
int interceptorIndex,
HttpServletRequest request,
HttpServletResponse response,
Exception ex) throws Exception {
// Apply afterCompletion methods of registered interceptors.
if (mappedHandler != null) {
HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
if (interceptors != null) {
for (int i = interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, mappedHandler.getHandler(), ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
}
}
}
}
}
triggerAfterCompletion做的事情就是从当前的拦截器开始逆向调用每个拦截器的afterCompletion方法,并且捕获它的异常,也就是说每个拦截器的afterCompletion方法都会调用。
根据以上的代码,分析一下不同拦截器及其方法的执行顺序。假设有5个拦截器编号分别为12345,若一切正常则方法的执行顺序是12345的preHandle,54321的postHandle,54321的afterCompletion。若编号3的拦截器的preHandle方法返回false或者抛出了异常,接下来会执行的是21的afterCompletion方法。这里要注意的地方是,我们在写一个拦截器的时候要谨慎的处理preHandle中的异常,因为这里一旦有异常抛出就不会再受到这个拦截器的控制。12345的preHandle的方法执行过之后,若handler出现了异常或者某个拦截器的postHandle方法出现了异常,则接下来都会执行54321的afterCompletion方法,因为只要12345的preHandle方法执行完,当前拦截器的拦截器就会记录成编号5的拦截器,而afterCompletion总是从当前的拦截器逆向的向前执行。
另外,实现HandlerInterceptor拦截器还有一个方法,就是实现WebRequestInterceptor接口。其实它和刚才的两种方法也是殊途同归,最终还是被spring适配成HandlerInterceptor。有一点不同,它的preHandle方法最终只会返回true。
4.MethodInterceptor拦截器
相关代码在 MethodInterceptor 包下
项目使用springboot
的2.3.0.RELEASE版本构建,其中需要注意导入aop
的starter
;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
注意: 在springboot
项目中若未加入上面的包,案例一会报错,案例二不会报错,但是不会生效;
4.1 非注解
以下两个案例都是针对非注解的
案例一
使用aspectj execution
表达定义切点;这个就比较灵活了,主要就是看traceExecution
怎么去写了;
切点表达式的写法
切点表达式的写法
表达式语法:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
- 访问修饰符可以省略
- 返回值类型、包名、类名、方法名可以使用星号* 代表任意
- 包名与类名之间一个点 . 代表当前包下的类,两个点 … 表示当前包及其子包下的类
- 参数列表可以使用两个点 … 表示任意个数,任意类型的参数列表
例如:
execution(public void com.itheima.aop.Target.method())
返回值类型任意 com.itheima.aop包下的Target类的 任意方法(参数个数任意 类型任意)
execution(void com.itheima.aop.Target.*(..))
返回值类型任意 com.itheima.aop包下的任意类的任意方法(参数个数任意,类型任意)
execution(* com.itheima.aop.*.*(..))
返回值类型任意 com.itheima.aop包及其子包下的任意方法(参数个数任意,类型任意)
execution(* com.itheima.aop..*.*(..))
execution(* *..*.*(..))
- 自己写一个类实现
MethodInterceptor
接口的invoke()
方法
public class MyInterceptor implements MethodInterceptor {
// 当方法被拦截 会调用其中的invoke方法
// 当一个方法调用到达一个被 AOP 代理的对象时,AOP 框架会捕获这个调用,并创建一个 MethodInvocation 对象。这个对象封装了原始的方法调用(包括方法本身、参数等)。
// MethodInvocation 对象随后被传递给拦截器的 invoke 方法。在 invoke 方法内部,你可以执行任何前置逻辑(比如记录日志、检查权限等)。
// 然后,当你调用 methodInvocation.proceed(); 时,AOP 框架会知道是时候执行原始方法了。
// 在 proceed 方法执行之后,你可以执行任何后置逻辑(比如记录执行时间、更新状态等)
// methodInvocation.proceed(); 的返回值是被代理对象的原始方法的返回值(如果有的话)。因此,你可以根据需要修改这个返回值,或者将其直接返回给原始调用者。
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("MyMethodInterceptor 这是执行的是飞注解方法的拦截器,拦截的方法名是 "+methodInvocation.getMethod().getName());
return methodInvocation.proceed();
}
}
-
使用
AspectJExpressionPointcut
定义切点并注册@Configuration public class InterceptorConfig { //注意该地址为项目具体包地址 public static final String traceExecution = "execution(* rejjie.MethodInterceptor..*(..))"; @Bean public DefaultPointcutAdvisor defaultPointcutAdvisor2() { // JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut(); // pointcut.setPattern("rejjie.MethodInterceptor.NoAnnoation.*"); AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); pointcut.setExpression(traceExecution); // 配置增强类advisor DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(); advisor.setPointcut(pointcut); advisor.setAdvice(new MyInterceptor()); return advisor; } }
执行结果
MyMethodInterceptor 这是执行的是飞注解方法的拦截器,拦截的方法名是 testAnnoation MyMethodInterceptor 这是执行的是飞注解方法的拦截器,拦截的方法名是 runAnnoation 注解的方法被执行了
案例二
这个案例主要是用
JdkRegexpMethodPointcut
来构造切点,这个就看Pattern
参数怎么写了;-
自己写一个类实现
MethodInterceptor
接口的invoke()
方法public class MyInterceptor implements MethodInterceptor { // 当方法被拦截 会调用其中的invoke方法 // 当一个方法调用到达一个被 AOP 代理的对象时,AOP 框架会捕获这个调用,并创建一个 MethodInvocation 对象。这个对象封装了原始的方法调用(包括方法本身、参数等)。 // MethodInvocation 对象随后被传递给拦截器的 invoke 方法。在 invoke 方法内部,你可以执行任何前置逻辑(比如记录日志、检查权限等)。 // 然后,当你调用 methodInvocation.proceed(); 时,AOP 框架会知道是时候执行原始方法了。 // 在 proceed 方法执行之后,你可以执行任何后置逻辑(比如记录执行时间、更新状态等) // methodInvocation.proceed(); 的返回值是被代理对象的原始方法的返回值(如果有的话)。因此,你可以根据需要修改这个返回值,或者将其直接返回给原始调用者。 @Override public Object invoke(MethodInvocation methodInvocation) throws Throwable { System.out.println("MyMethodInterceptor 这是执行的是非注解方法2的拦截器,拦截的方法名是 "+methodInvocation.getMethod().getName()); return methodInvocation.proceed(); } }
-
-
使用
JdkRegexpMethodPointcut
定义切点@Configuration public class InterceptorConfig { //注意该地址为项目具体包地址 @Bean public DefaultPointcutAdvisor defaultPointcutAdvisor2() { JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut(); pointcut.setPattern("rejjie.MethodInterceptor..*"); // 配置增强类advisor DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(); advisor.setPointcut(pointcut); advisor.setAdvice(new MyInterceptor()); return advisor; } }
执行结果
MyMethodInterceptor 这是执行的是非注解方法2的拦截器,拦截的方法名是 testAnnoation
MyMethodInterceptor 这是执行的是非注解方法2的拦截器,拦截的方法名是 runAnnoation
注解的方法被执行了
两者之间的不同是 定义切点的方式不同
4.2 注解方式
自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InterceptorAnnotation {
}
案例三
这个案例就是案例一,只是将AspectJExpressionPointcut
的参数改变了Expression
;
直接使用案例一代码,然后将traceExecution
修改就可以了
public static final String traceExecution = "annotation(rejjie.MethodInterceptor.Annotaion.InterceptorAnnotation)";
同时要在被 拦截的方法上 加上自定义的注解 @InterceptorAnnotation 才能进行拦截
@InterceptorAnnotation
public void runAnnoation(){
System.out.println("注解的方法被执行了");
}
执行结果
此时执行的是注解方法拦截器runAnnoation
注解的方法被执行了
案例四
这个案例就是使用AnnotationMatchingPointcut
来构造切点;
- 自己写一个类实现
MethodInterceptor
接口的invoke()
方法
public class MyInterceptor2 implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("此时执行的是注解方法拦截器"+ methodInvocation.getMethod().getName());
return methodInvocation.proceed();
}
}
2.使用AnnotationMatchingPointcut构造切点
AnnotationMatchingPointcut pointcut = new AnnotationMatchingPointcut(InterceptorAnnotation.class, true);
@Configuration
public class InterceptorConfig1 {
//注意该地址为项目具体包地址
// public static final String traceExecution = "annotation(rejjie.MethodInterceptor.Annotaion.InterceptorAnnotation)";
// public static final String traceExecution = "annotation(rejjie.MethodInterceptor.Annotaion.InterceptorAnnotation)";
@Bean
public DefaultPointcutAdvisor defaultPointcutAdvisor3() {
MyInterceptor2 interceptor = new MyInterceptor2();
AnnotationMatchingPointcut annotationMatchingPointcut = new AnnotationMatchingPointcut(null, InterceptorAnnotation.class);
// 配置增强类advisor
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
advisor.setPointcut(annotationMatchingPointcut);
advisor.setAdvice(interceptor);
return advisor;
}
}
执行结果
此时执行的是注解方法拦截器runAnnoation
注解的方法被执行了
最终所有的方法都是springAOP,具体可以放在springAop中去学习
参考
Spring Boot拦截器精讲 (biancheng.net)
https://blog.csdn.net/guoychuan/article/details/106628874