目录
前言
正文
拦截器的介绍
拦截器的使用
拦截器结合ThreadLocal使用
总结
可能很多小伙伴对于SpringMVC的学习比较薄弱,在公司上班中看见项目中大量使用SpringMVC拦截器做一些请求前后的判断工作。那么此帖对SpringMVC的拦截器做一个介绍和使用。
拦截器也就是AOP概念下的一个产物。何为拦截器也就是在不改变原有请求方法的代码上对请求逻辑做一些分支改变或者是添加一些内容给请求内容。
SpringMVC定义了一个拦截器的接口,接口中定义了几个方法,不同方法会在不同时机被调用。
HandlerInterceptor接口介绍preHandle():在指定的请求执行之前执行,此方法的返回值决定请求是否继续执行
postHandle():在指定的请求方法的业务代码(Controller层方法)执行完毕之后执行
afterCompletion():在指定的整个请求执行完毕(视图渲染后)后执行
概念熟悉后,那么下面就看一个拦截器的使用Dome把。
/**
* @Author liha
* @Date 2022-03-12 9:54
* 李哈YYDS
*/
public class TestInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("parHandle");
return HandlerInterceptor.super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion");
}
}
定义一个类实现HandlerInterceptor拦截器接口,并且重写其中三个方法,暂时就输出一些日志把,大家熟悉每个方法的作用后可根据公司业务来做具体的设计。
此时我们定义好了自己的拦截器规定,但是并没有把拦截器规定放入到SpringMVC中,那么对于Spring boot整合的SpringMVC来说,如何把规定放入到Spring MVC中呢?
/**
* @Author liha
* @Date 2022-03-12 10:06
* 李哈YYDS
*/
@Configuration
public class InterceptorConfig extends WebMvcConfigurationSupport {
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TestInterceptor()).addPathPatterns("/**");
}
}
WebMvcConfigurationSupport:此类中定义了一些Web规范的自定义方法,比如拦截器、异常解析器、视图解析器等等....在Spring boot自动配置SpringMVC的配置时会通过此类加载RequestMappingHandlerMapping到IoC容器中,在加载过程中会把各种自定义配置给加到RequestMappingHandlerMapping,而RequestMappingHandlerMapping也是SpringMVC中很重要的一个承上启下的组件。这里就不对底层做一个很仔细的讲解了,后面会有文章仔细讲解拦截器的加载和使用的底层源码。
把我们自定义的拦截器加载到Spring MVC中后,编写好我们的Controller层的代码后,启动Spring boot容器来测试一下。
/**
* @Author liha
* @Date 2022-02-05 13:01
* 李哈YYDS
*/
@RestController
public class SpringController {
@RequestMapping("/test")
public String test() throws InterruptedException {
System.out.println("this is hello world in Spring Boot.");
return "this is hello world in Spring Boot.";
}
}
可以清楚的看到执行流程入下图所示
为什么要引进ThreadLocal呢?
首先要明白ThreadLocal的作用!
ThreadLocal:
- 不同线程之间变量的隔离
- 同一个线程之间变量共享
如果说对于我们的拦截器和Controller层service层来说如果需要操作同一个局部变量,就可以使用到ThreadLocal,并且ThreadLocal是每个线程都维护一个局部变量,所以线程隔离保证了每个局部变量都是并发安全的。对于ThreadLocal没有学习过的小伙伴可以点下面链接,讲的特别仔细。
ThreadLocal的使用和底层源码解析https://blog.csdn.net/qq_43799161/article/details/123269910?spm=1001.2014.3001.5501那么我们来改变一下原有代码把。
拦截器代码块
/**
* @Author liha
* @Date 2022-03-12 9:54
* 李哈YYDS
*/
public class TestInterceptor implements HandlerInterceptor {
public final static ThreadLocal THREAD_LOCAL = new ThreadLocal<>();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 这里大家可以根据自己业务来做处理,可以是处理request对象,也可以使从里handler
// 我这里就测试一下
Cat cat = new Cat();
cat.setName("tomcat");
THREAD_LOCAL.set(cat);
System.out.println("parHandle");
return HandlerInterceptor.super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 这里大家可以根据自己业务来做处理,可以是处理response对象,也可以使从里handler,也可以处理modelAndView
// 我这里就测试一下
System.out.println("postHandle");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion");
}
}
controller层代码块
/**
* @Author liha
* @Date 2022-02-05 13:01
* 李哈YYDS
*/
@RestController
public class SpringController {
@RequestMapping("/test")
public String test() {
Cat cat = TestInterceptor.THREAD_LOCAL.get();
System.out.println("拦截器过来的数据"+cat.getName());
System.out.println("this is hello world in Spring Boot.");
return "this is hello world in Spring Boot.";
}
}
实体类代码块
/**
* @Author liha
* @Date 2022-03-12 10:26
* 李哈YYDS
*/
public class Cat {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
我们在原有的代码上加入了ThreadLocal,泛型为Cat类,当然大家可以根据自己业务定制Vo类,线程共享定制的Vo类。
这里就可以实现拦截器创建实体类,在Controller层使用,实现了线程之间的变量共享。
这里给大家稍微提一下源码把,为什么拦截器和控制层是一个线程?
大家知道对于SpringMVC来处理请求是走的DispatchServlet这个类的doDispatch(不知道也没关系)执行流程就是根据HandlerMapping获取到当前请求对应的类和方法(Controller层),再获取到拦截器的信息,包装成一个HandlerExecutionChain,然后通过HandlerAdapter来解析HandlerExecutionChain中的Controller层的请求对应的方法,并且通过反射执行方法返回一个ModelAndView,然后就视图解析啥的,后面的流程就不说了。但是在这些过程中,当HandlerExecutionChain获取到拦截器就会去执行具体的拦截器逻辑,所以拦截器请求映射的方法都是在doDispatch()方法中执行,所以拦截器和执行方法体都是在一个线程中,所以就可以通过ThreadLocal线程变量共享。如下图所示。
具体的源码就不过多说,后面会有文章来仔细解析SpringMVC拦截器的源码。
本帖中对于案例没有过多的去设计,就使用的测试案例来实现了一个简单拦截器,大家可以从简单的案例学习到拦截器接口中每个方法的具体执行时机,再来根据公司的业务来做一个处理。
最后,如果本帖对大家有帮助,希望大家点赞+关注!您的支持是给我最大的动力,后续一直会出各种框架的使用和源码解读!~