先說問題,如果你已經知道怎麼處理,請忽略。
項目中有個全局跨域配置,正常請求前端不會有跨域問題,如果在攔截器中拋出錯誤,前端就會有問題。
原因分析
既然在攔截器中報錯,就有跨域問題,那就說明這個跨域配置並沒有起作用。
當時的跨域配置是如下這樣
@Configuration
public class CorsConfiguration implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowCredentials(true)
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.maxAge(3600);
}
}
那麼這個配置爲什麼沒起作用呢?我們先來看一張圖。
這是Spring MVC的流程圖,用戶所有的請求都會經過DispatcherServlet,我們從DispatcherServlet這裏開始分析,找到doDispatch方法,他上面有這樣的註釋。
/**
* Process the actual dispatching to the handler.
*The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
*All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
注意其中的All HTTP methods are handled by this method
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//...省略
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//...省略
}
看這個方法,其他都忽略,只看getHandler(processedRequest),它會獲得HandlerExecutionChain 攔截器責任鏈,進入該方法
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
這只是一個循環,裏面還有一個getHandler(request)方法,進入該方法
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
//判斷是否是跨域請求
if (CorsUtils.isCorsRequest(request)) {
//獲取全局的跨域配置
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
//獲取方法或者類中定義的跨域配置
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
//合併配置
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
//獲取新的HandlerExecutionChain
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
代碼變多了,在這裏看到了我們需要的CorsConfiguration,把注意集中在這部分,進入getCorsHandlerExecutionChain(request, executionChain, config)看看
protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
HandlerExecutionChain chain, @Nullable CorsConfiguration config) {
if (CorsUtils.isPreFlightRequest(request)) {
HandlerInterceptor[] interceptors = chain.getInterceptors();
chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);
}
else {
chain.addInterceptor(new CorsInterceptor(config));
}
return chain;
}
這裏首先會判斷請求是否是預檢請求,預檢請求是OPTIONS方法,用於檢查服務器是否接受前端過來的請求
不是預檢請求走else,這裏將跨域配置組裝成了一個Interceptor並加入到攔截器責任鏈中,進入addInterceptor方法中
public void addInterceptor(HandlerInterceptor interceptor) {
initInterceptorList().add(interceptor);
}
//initInterceptorList()
private List<HandlerInterceptor> initInterceptorList() {
if (this.interceptorList == null) {
this.interceptorList = new ArrayList<>();
if (this.interceptors != null) {
// An interceptor array specified through the constructor
CollectionUtils.mergeArrayIntoCollection(this.interceptors, this.interceptorList);
}
}
this.interceptors = null;
return this.interceptorList;
}
你會發現initInterceptorList()其實就是一個List,而我們的跨域攔截器就被放入List的最後一個位置
因此你在自定義攔截器中拋出錯誤,是並不會執行到跨域攔截器的,而是直接返回了。
解決方案
使用Filter過濾器來處理跨域請求,修改後的配置
@Configuration
public class CorsConfig{
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.setAllowCredentials(true);
config.addAllowedMethod("*");
config.addAllowedHeader("*");
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
configSource.registerCorsConfiguration("/**", config);
return new CorsFilter(configSource);
}
}
使用Filter就可以是因爲,Filter在Servlet前後起作用,和執行順序
下面是兩者的區別
執行順序
過濾器前->攔截器前->Action處理->攔截器後->過濾器後
本文分享自微信公衆號 - 阿提說說(itsaysay)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。