Spring MVC处理CORS请求的流程
Spring MVC处理任何一个reuqest请求都会去找到它的一个处理器Handler,因此首当其冲就来到DispatcherServlet#getHandler()这个方法~
getHandler()
对于Spring MVC来说,每处理一个request请求都应该对应着一个Handler:就是DispatcherServlet.getHandler()方法来找到其对应的处理器:
DispatcherServlet: // 根据HttpRequest从handlerMappings找到对应的handler @Nullable protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { // 开启Spring MVC后默认情况下handlerMappings的长度是4 for (HandlerMapping mapping : this.handlerMappings) { HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { return handler; } } } return null; }
handlerMappings它的长度默认是3,内容如下:
处理本例请求的是RequestMappingHandlerMapping,获取处理器的方法在父类上:
AbstractHandlerMapping: // 默认使用的是UrlBasedCorsConfigurationSource来管理跨域配置 private CorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource(); // 使用的都是本类的pathMatcher和urlPathHelper public void setCorsConfigurations(Map<String, CorsConfiguration> corsConfigurations) { Assert.notNull(corsConfigurations, "corsConfigurations must not be null"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.setCorsConfigurations(corsConfigurations); source.setPathMatcher(this.pathMatcher); source.setUrlPathHelper(this.urlPathHelper); this.corsConfigurationSource = source; } // @since 5.1 此方法出现较晚,但一般也不建议去设置 public void setCorsConfigurationSource(CorsConfigurationSource corsConfigurationSource) { Assert.notNull(corsConfigurationSource, "corsConfigurationSource must not be null"); this.corsConfigurationSource = corsConfigurationSource; } @Override @Nullable public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { // getHandlerInternal这个方法是根据URL去匹配一个Handler,当然有可能是匹配不上的,那么handler就为null Object handler = getHandlerInternal(request); if (handler == null) { handler = getDefaultHandler(); } // 若最终还是为null,那就返回null 后续的也就不再处理了 // 它的结果是:交给下一个HandlerMapping处理,若所有的处理完后还是返回null。 // 那就noHandlerFound(processedRequest, response) --> 404 if (handler == null) { return null; } ... HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); ... // 若是跨域请求,这里就继续处理,也是本文讲述具有差异性的地方所在 if (CorsUtils.isCorsRequest(request)) { // 1、全局配置:从UrlBasedCorsConfigurationSource找到一个属于这个请求的配置 // 请注意:若三种方式都没有配置,这里返回的就是null~~~ CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request); // 2、从handler自己里找:若handler自己实现了CorsConfigurationSource接口,那就从自己这哪呗 // 说明:此种方式适用于一个类就是一个处理器的case。比如servlet处理器 // 所以对于@RequestMapping情况,这个值大部分情况都是null CorsConfiguration handlerConfig = getCorsConfiguration(handler, request); // 3、把全局配置和handler配置combine组合合并 CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig); // 4、这个方法很重要。请看下面这个方法 executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } } // @since 4.2 protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request, HandlerExecutionChain chain, @Nullable CorsConfiguration config) { // 若是预检请求:就new一个新的HandlerExecutionChain。 // PreFlightHandler是一个HttpRequestHandler哦~~~并且实现了接口CorsConfigurationSource if (CorsUtils.isPreFlightRequest(request)) { HandlerInterceptor[] interceptors = chain.getInterceptors(); chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors); } // 若不是预检请求,就添加一个拦截器CorsInterceptor // 注意:这个拦截器只会作用于这个chain哦(也就是这个handler~~~) // 能进来这里是简单请求 或者 真实请求。 else { chain.addInterceptor(new CorsInterceptor(config)); } return chain; }
根据URL成功匹配到一个Handler后,若是跨域请求就会继续添加跨域部分的处理逻辑:
- 若是预检请求:针对此请求会直接new一个PreFlightHandler作为HttpRequestHandler处理器来处理它,而不再是交给匹配上的Handler去处理(这点特别的重要)
- PreFlightHandler#handle方法委托给了corsProcessor去处理跨域请求头、响应头的
- 值得注意的是:此时即使原Handler它不执行了,但匹配上的HandlerInterceptor们仍都还是会生效执行作用在OPTIONS方法上的
- 若是简单请求/真实请求:在原来的处理链上加一个拦截器chain.addInterceptor(new CorsInterceptor(config)),由这个拦截器它最终复杂来处理相关逻辑(全权委托给corsProcessor)
核心的处理步骤就这么简单,理解起来也并不困难。因此我们还非常有必要的就是这三种配置方式是如何被初始化的呢?
CorsFilter方式初始化
要让它生效就需要我们手动把它注册进Servlet容器内,由它“拦截请求”自己来完成CorsProcessor.processRequest(corsConfiguration, request, response)这些处理操作。所以它和后续的getHandler()等这些处理逻辑是关系不大的。
此种方式的优雅程度上和自己实现差异并不大,因此我个人是不太推荐的~~
WebMvcConfigurer.addCorsMappings()方式初始化
这种方式是我推荐的,它的基本原理和我之前说过的WebMvcConfigurer其它配置项差不多。它作用的地方就是下面我列出的4个HandlerMapping初始化的时候。
WebMvcConfigurationSupport: @Bean public RequestMappingHandlerMapping requestMappingHandlerMapping() { ... } // 最终返回的是个SimpleUrlHandlerMapping 可以直接完成映射 @Bean @Nullable public HandlerMapping viewControllerHandlerMapping() { ViewControllerRegistry registry = new ViewControllerRegistry(this.applicationContext); ... } // 按照bean名称进行匹配处理器 @Bean public BeanNameUrlHandlerMapping beanNameHandlerMapping() {} // 最终也是个SimpleUrlHandlerMapping @Bean @Nullable public HandlerMapping resourceHandlerMapping() {}
他们四个初始化时最终都调用了同一个方法:mapping.setCorsConfigurations(getCorsConfigurations())设置CORS配置,此方法是父类AbstractHandlerMapping提供的,原理可参考CorsRegistry和CorsRegistration .