2019独角兽企业重金招聘Python工程师标准>>>
先说问题,如果你已经知道怎么处理,请忽略。
项目中有个全局跨域配置,正常请求前端不会有跨域问题,如果在拦截器中抛出错误,前端就会有问题。
原因分析
既然在拦截器中报错,就有跨域问题,那就说明这个跨域配置并没有起作用。
当时的跨域配置是如下这样
@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 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处理->拦截器后->过滤器后