為什麼有時候spring mvc的interceptor會執行兩次

林舍發表於2017-10-24

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方法是哪個執行緒在呼叫呢?各位如果感興趣的話可以深入研究一下!

相關文章