引言
本文的目标是解决以下几个疑问:
- 怎么在Spring Boot中使用Interceptor?
- 怎么定制多个Interceptor的顺序?
- 怎么针对特定路径进行拦截?
在Spring Boot中使用Interceptor
这里我新建了一个项目来展示相关的案例。整个项目的结构如下:
可以看到项目非常简单,1个Controller,1个配置类,3个拦截类。实现拦截器最重要的步骤可以分为两步:
- 创建拦截器类,实现HandlerInterceptor接口。
- 创建配置类,实现WebMvcConfigurer接口,并重写addInterceptors(InterceptorRegistry registry)方法。
这里有一点要特别说明一下,我项目的版本是Spring boot2.2.2,内置Spring是5,这个版本有个特点就是大部分接口都用了默认方法。按照很多博客的写法,在第二步实现的是WebMvcConfigurerAdapter,这样也可以,但是会提示你说该类已经过期了。
1. 创建拦截器类
如果你用的Spring版本是比较旧的,那么实现HandlerInterceptor 会默认让你覆写3个方法。但是这个版本有默认方法,所以覆写不是必需的,这里就覆写两个方法一个是preHandle,另一个是postHandle。preHandle指的是在拦截器工作之前,而postHandle代表拦截器工作之后。
MyInterceptor类的代码如下:而MyInterceptor2,MyInterceptor3两个类代码除了类名不一样,其余都一样。
@Component
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(getClass() + " pre");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println(getClass() + " post");
}
}
这里我们先展示pre都为true的情况。需要注意的是如果preHandler返回的是false。拦截器就会中断。
2.创建配置类
@Configuration
public class MyConfig implements WebMvcConfigurer {
@Autowired
MyInterceptor myInterceptor;
@Autowired
MyInterceptor2 myInterceptor2;
@Autowired
MyInterceptor3 myInterceptor3;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor);
registry.addInterceptor(myInterceptor2);
registry.addInterceptor(myInterceptor3);
}
}
这里我没有对某个路径做拦截,而是全部拦截。下面展示下Controller类:
@Controller
public class MyController {
@RequestMapping(value = "/index", method = RequestMethod.GET)
@ResponseBody
public String index() {
return "新鲜出炉的炸鸡!!";
}
}
用postman请求:
查看控制台输出:
class com.example.interceptor_demo.MyInterceptor pre
class com.example.interceptor_demo.MyInterceptor2 pre
class com.example.interceptor_demo.MyInterceptor3 pre
class com.example.interceptor_demo.MyInterceptor3 post
class com.example.interceptor_demo.MyInterceptor2 post
class com.example.interceptor_demo.MyInterceptor post
可以看到所有pre执行完才执行post。执行逻辑是:
MyInterceptor pre -----> MyInterceptor2 pre -------> MyInterceptor3 pre
MyInterceptor post <----- MyInterceptor2 pre <------- MyInterceptor3 pre
这里我们修改一下MyInterceptor2,让它的pre返回false。
首先我们可以看到界面没有任何字样:
任何看控制台的输出:
class com.example.interceptor_demo.MyInterceptor pre
class com.example.interceptor_demo.MyInterceptor2 pre
也就是说执行完MyInterceptor2 的打印以后就不再执行其他的拦截器。并且这些handler也不会进入到postHandler方法里。
执行逻辑在2断了以后,后面的都不会执行了:
MyInterceptor pre -----> MyInterceptor2 pre ----\---> MyInterceptor3 pre
MyInterceptor post <----- MyInterceptor2 pre <------- MyInterceptor3 pre
定制顺序
简单粗暴方式A
改变顺序的一种方式,简单粗暴,直接修改添加顺序:
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor2);
registry.addInterceptor(myInterceptor);
registry.addInterceptor(myInterceptor3);
}
控制台输出为:
class com.example.interceptor_demo.MyInterceptor2 pre
class com.example.interceptor_demo.MyInterceptor pre
class com.example.interceptor_demo.MyInterceptor3 pre
class com.example.interceptor_demo.MyInterceptor3 post
class com.example.interceptor_demo.MyInterceptor post
class com.example.interceptor_demo.MyInterceptor2 post
查看源码我们可以知道:
public InterceptorRegistration addInterceptor(HandlerInterceptor interceptor) {
InterceptorRegistration registration = new InterceptorRegistration(interceptor);
this.registrations.add(registration);
return registration;
}
// 而 registrations 是一个 ArrayList
List<InterceptorRegistration> registrations = new ArrayList<>();
微微复杂方法B
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor).order(10);
registry.addInterceptor(myInterceptor2);
registry.addInterceptor(myInterceptor3);
}
在这里可以直接指定顺序,需要注意的首先是order默认值是0。
然后是order越大的越后。
MyInterceptor3 pre -----> MyInterceptor2 pre -------> MyInterceptor pre
MyInterceptor3 post <----- MyInterceptor2 pre <------- MyInterceptor pre
实际上,获取拦截器列表是通过这个流方法。流先对List做了排序再进行返回:
protected List<Object> getInterceptors() {
return this.registrations.stream()
.sorted(INTERCEPTOR_ORDER_COMPARATOR)
.map(InterceptorRegistration::getInterceptor)
.collect(Collectors.toList());
}
针对特定路径
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor).addPathPatterns("/index");
registry.addInterceptor(myInterceptor2).addPathPatterns("/home");
registry.addInterceptor(myInterceptor3).addPathPatterns("/login");
}
addPathPatterns方法可以指定特定路径。控制台会打印出:
class com.example.interceptor_demo.MyInterceptor pre
class com.example.interceptor_demo.MyInterceptor post
这表明只被myInterceptor拦截,而不被2,3拦截。
继续做修改:
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(myInterceptor).excludePathPatterns("/index");
registry.addInterceptor(myInterceptor2).addPathPatterns("/home");
registry.addInterceptor(myInterceptor3);
}
可以看到excludePathPatterns可以排除特定路径:
class com.example.interceptor_demo.MyInterceptor3 pre
class com.example.interceptor_demo.MyInterceptor3 post
后言
在这里项目例子为了便于展示就没有分包了,在实际工作中,最好是分包,例如多个拦截器放到一个包里,配置类一个包之类的。
一些类的补充
InterceptorDemoApplication 类如下。
@SpringBootApplication
public class InterceptorDemoApplication {
public static void main(String[] args) {
SpringApplication.run(InterceptorDemoApplication.class, args);
}
}
这里再提供一下pom.xml。实际上在Spring initializr勾选的时候只勾选了一个web。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>interceptor_demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>interceptor_demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-thymeleaf</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>