为什么有时候spring mvc的interceptor会执行两次

spring mvc的拦截器大家应该都不陌生,可以在进入响应controller之前以及之后进行一些处理。
但有些情况下,拦截器中的preHandle方法总会执行两次,这是为何?

在解答此问题之前,我们先创建一个简单的controller

@RestController
public class HelloWorld {
    @GetMapping("/hello")
    public String hello() {
        System.out.println("== Hello ==");
        return "hello";
    }
}

为了更加清楚的描述问题,这里创建两个拦截器

@Configuration
public class WebConfig extends DelegatingWebMvcConfiguration {
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new HandlerInterceptorAdapter() {
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                System.out.println("====> Pre Handle A " + Thread.currentThread().getId());
                return true;
            }

            @Override
            public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
                System.out.println("After Completion A <==== " + Thread.currentThread().getId());
            }

            @Override
            public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                System.out.println("After Concurrent Started A <==== " + Thread.currentThread().getId());
            }

            @Override
            public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
                System.out.println("Post Handle A <==== " + Thread.currentThread().getId());
            }
        }).addPathPatterns("/**").order(1);

        registry.addInterceptor(new HandlerInterceptorAdapter() {
            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                System.out.println("====> Pre Handle B " + Thread.currentThread().getId());
                return true;
            }

            @Override
            public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
                System.out.println("After Completion B <==== " + Thread.currentThread().getId());
            }

            @Override
            public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                System.out.println("After Concurrent Started B <==== " + Thread.currentThread().getId());
            }

            @Override
            public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
                System.out.println("Post Handle B <==== " + Thread.currentThread().getId());
            }
        }).addPathPatterns("/**").order(2);

        super.addInterceptors(registry);
    }
}

在拦截器实现中,我们特意打印了当前线程的线程号。

访问 /hello ,我们可以得到如下输出

====> Pre Handle A 68
====> Pre Handle B 68
== Hello ==
Post Handle B <==== 68
Post Handle A <==== 68
After Completion B <==== 68
After Completion A <==== 68

可以看到,spring interceptor的运行机制就像剥洋葱一样,且对于同一个请求整个运行过程在同一个线程内完成。

接下来定义另一个controller,并返回StreamingResponseBody

@RestController
public class HelloWorld {
    @GetMapping("/streaming")
    public StreamingResponseBody streaming() {
        System.out.println("== Streaming ==");
        return (OutputStream outputStream) -> {
            outputStream.write("streaming".getBytes());
            outputStream.flush();
            outputStream.close();
        };
    }

    @GetMapping("/hello")
    public String hello() {
        System.out.println("== Hello ==");
        return "hello";
    }
}

此时访问 /streaming,可以得到如下输出

====> Pre Handle A 74
====> Pre Handle B 74
== Streaming ==
After Concurrent Started B <==== 74
After Concurrent Started A <==== 74
====> Pre Handle A 75
====> Pre Handle B 75
Post Handle B <==== 75
Post Handle A <==== 75
After Completion B <==== 75
After Completion A <==== 75

从输出中可以看出,整个请求过程使用了两个线程,并且调用了拦截器中的afterConcurrentHandlingStarted方法。到这里各位看官应该就明白了,对于concurrent类型的返回值,spring会启用一个新的线程来处理concurrent类型消息,在新的线程中会重新调用preHandle方法。

那,postHandle方法是哪个线程在调用呢?各位如果感兴趣的话可以深入研究一下!


订阅号

阅读 6.5k

推荐阅读
林中小舍
用户专栏

工作中的坑点及经验

51 人关注
41 篇文章
专栏主页