引言

本文的目标是解决以下几个疑问:

  1. 怎么在Spring Boot中使用Interceptor?
  2. 怎么定制多个Interceptor的顺序?
  3. 怎么针对特定路径进行拦截?

在Spring Boot中使用Interceptor

这里我新建了一个项目来展示相关的案例。整个项目的结构如下:

springboot 拦截器里读取json springboot2 拦截器_spring


可以看到项目非常简单,1个Controller,1个配置类,3个拦截类。实现拦截器最重要的步骤可以分为两步

  1. 创建拦截器类,实现HandlerInterceptor接口。
  2. 创建配置类,实现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请求:

springboot 拦截器里读取json springboot2 拦截器_maven_02


查看控制台输出:

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。

首先我们可以看到界面没有任何字样:

springboot 拦截器里读取json springboot2 拦截器_拦截器_03


任何看控制台的输出:

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>