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