springboot拦截器

拦截器

拦截器

1. 拦截器的概念和作用

1.1 什么是拦截器

​ 拦截器(Interceptor)是一种特殊的组件,它可以在请求处理的过程中对请求和响应进行拦截和处理。拦截器可以在请求到达目标处理器之前、处理器处理请求之后以及视图渲染之前执行特定的操作。拦截器的主要目的是在不修改原有代码的情况下,实现对请求和响应的统一处理。

1.2 拦截器的作用

拦截器可以用于实现以下功能:

​ 权限控制:拦截器可以在请求到达处理器之前进行权限验证,从而实现对不同用户的访问控制。
​ 日志记录:拦截器可以在请求处理过程中记录请求和响应的详细信息,便于后期分析和调试。
​ 接口幂等性校验:拦截器可以在请求到达处理器之前进行幂等性校验,防止重复提交。
​ 数据校验:拦截器可以在请求到达处理器之前对请求数据进行校验,确保数据的合法性。
​ 缓存处理:拦截器可以在请求处理之后对响应数据进行缓存,提高系统性能。

1.3 拦截器与过滤器的区别

拦截器和过滤器都可以实现对请求和响应的拦截和处理,但它们之间存在以下区别:

执行顺序:过滤器在拦截器之前执行,拦截器在处理器之前执行。
功能范围:过滤器可以对所有请求进行拦截,而拦截器只能对特定的请求进行拦截。
生命周期:过滤器由Servlet容器管理,拦截器由Spring容器管理。
使用场景:过滤器适用于对请求和响应的全局处理,拦截器适用于对特定请求的处理。

2. springboot拦截器的实现

在 Spring Boot 项目中,使用拦截器功能通常需要以下 3 步:

  1. 定义拦截器;

  2. 注册拦截器;

  3. 指定拦截规则(如果是拦截所有,静态资源也会被拦截)

2.1 定义拦截器
返回值类型方法声明描述
booleanpreHandle(HttpServletRequest request, HttpServletResponse response, Object handler)该方法在控制器处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回 false 表示中断后续操作。
voidpostHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)该方法在控制器处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步修改。
voidafterCompletion(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版本构建,其中需要注意导入aopstarter

        <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(* *..*.*(..))
  1. 自己写一个类实现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();
    }
}
  1. 使用 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参数怎么写了;

    1. 自己写一个类实现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();
          }
      }
      
  2. 使用 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来构造切点;

  1. 自己写一个类实现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

SpringBoot拦截器是一种用于拦截和处理请求的组件。在SpringBoot中,我们可以通过实现`HandlerInterceptor`接口或者继承`HandlerInterceptorAdapter`类来创建自定义的拦截器拦截器可以在请求被处理之前和之后执行一些操作,比如验证用户身份、记录日志等。 在SpringBoot中,拦截器的配置可以通过实现`WebMvcConfigurer`接口来完成。我们可以在`addInterceptors`方法中添加我们自定义的拦截器,并指定拦截的路径。例如,我们可以使用`registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**")`来添加一个拦截器,并拦截所有的请求。 拦截器与过滤器的区别在于归属和内容。过滤器属于Servlet技术,而拦截器属于SpringMVC技术。过滤器对所有访问进行增强,而拦截器仅针对SpringMVC的访问进行增强。在SpringBoot中,我们可以通过配置类来添加拦截器,如`WebConfig`和`AdminWebConfig`。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* *3* [Springboot——拦截器](https://blog.csdn.net/weixin_51351637/article/details/128058053)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Springboot实现拦截器功能](https://blog.csdn.net/weixin_52875557/article/details/123793361)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白鼠666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值