先说问题,如果你已经知道怎么处理,请忽略。
项目中有个全局跨域配置,正常请求前端不会有跨域问题,如果在拦截器中抛出错误,前端就会有问题。
原因分析
既然在拦截器中报错,就有跨域问题,那就说明这个跨域配置并没有起作用。
当时的跨域配置是如下这样@Configurationpublic 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. * request current HTTP request * response current HTTP response * 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 ListinitInterceptorList() { 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过滤器来处理跨域请求,修改后的配置
@Configurationpublic 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处理->拦截器后->过滤器后