HandlerMappingIntrospector
HandlerMapping内省器。它是一个帮助类用于从HandlerMapping里获取信息,这些信息用于服务特定的请求。@EnableWebMvc默认会把它放进容器里,开发者可以@Autowired拿来使用(框架内部木有使用)
这个类比较重要,Spring Cloud Netflix Zuul巧用它实现了一些功能~
// @since 4.3.1 public class HandlerMappingIntrospector implements CorsConfigurationSource, ApplicationContextAware, InitializingBean { @Nullable private ApplicationContext applicationContext; @Nullable private List<HandlerMapping> handlerMappings; ... // 生路一些构造函数、set方法 // 1、Map<String, HandlerMapping> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, HandlerMapping.class, true, false) // 2、如果第一步获取到了Beans,sort()排序一下 // 3、若没找到,回退到`DispatcherServlet.properties`这个配置文件里去找 @Override public void afterPropertiesSet() { if (this.handlerMappings == null) { Assert.notNull(this.applicationContext, "No ApplicationContext"); this.handlerMappings = initHandlerMappings(this.applicationContext); } } // 从这些HandlerMapping找到MatchableHandlerMapping // 若一个都木有,此方法抛出异常 @Nullable public MatchableHandlerMapping getMatchableHandlerMapping(HttpServletRequest request) throws Exception { ... } @Override @Nullable public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { Assert.notNull(this.handlerMappings, "Handler mappings not initialized"); HttpServletRequest wrapper = new RequestAttributeChangeIgnoringWrapper(request); for (HandlerMapping handlerMapping : this.handlerMappings) { HandlerExecutionChain handler = null; try { handler = handlerMapping.getHandler(wrapper); } catch (Exception ex) { // Ignore } if (handler == null) { continue; } // 拿到作用在此Handler上的所有的拦截器们:HandlerInterceptor // 若有拦截器实现了CorsConfigurationSource接口,那就返回此拦截器上的CORS配置源 if (handler.getInterceptors() != null) { for (HandlerInterceptor interceptor : handler.getInterceptors()) { if (interceptor instanceof CorsConfigurationSource) { return ((CorsConfigurationSource) interceptor).getCorsConfiguration(wrapper); } } } // 若这个Handle本身(注意:并不是所有的handler都是一个方法,也可能是个类,所以也有可能是会实现接口的) // 就是个CorsConfigurationSource 那就以它的为准 if (handler.getHandler() instanceof CorsConfigurationSource) { return ((CorsConfigurationSource) handler.getHandler()).getCorsConfiguration(wrapper); } } return null; } }
这个自省器最重要的功能就是初始化的时候把所有的HandlerMapping都拿到了。这个处理逻辑和DispatcherServlet.initHandlerMappings是一样的,为何不提取成公用的呢???
它另外一个功能便是获取HttpServletRequest对应的CORS配置信息:
- 从作用在此Handler的拦截器HandlerInterceptor上获取
- 若拦截器里木有,那就从Handler本身获取(若实现了CorsConfigurationSource接口)
- 都没有就返回null
CorsInterceptor
Cors拦截器。它最终会被放到处理器链HandlerExecutionChain里,用于拦截处理(最后一个拦截)。
private class CorsInterceptor extends HandlerInterceptorAdapter implements CorsConfigurationSource { @Nullable private final CorsConfiguration config; //拦截操作 最终是委托给了`CorsProcessor`,也就是DefaultCorsProcessor去完成处理的 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return corsProcessor.processRequest(this.config, request, response); } @Override @Nullable public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { return this.config; } }
该拦截器是AbstractHandlerMapping的私有内部类,它会在每次getHandler()的时候放进去专门作用域当前跨域的请求,具体的流程在下个章节里有讲述。
PreFlightHandler
这个和上面的CorsInterceptor互斥,它最终也是委托给corsProcessor来处理请求,只是它是专门用于处理预检请求的。详见CORS请求处理流程部分。
CorsProcessor(重要)
它便是CORS真正处理器:用于接收请求和一个配置,然后更新Response:比如接受/拒绝
public interface CorsProcessor { // 根据所给的`CorsConfiguration`来处理请求 boolean processRequest(@Nullable CorsConfiguration configuration, HttpServletRequest request, HttpServletResponse response) throws IOException; }
它的唯一实现类是DefaultCorsProcessor
DefaultCorsProcessor
它遵循的是W3C标准实现的。Spring MVC中对CORS规则的校验,都是通过委托给 DefaultCorsProcessor实现的
// @since 4.2 public class DefaultCorsProcessor implements CorsProcessor { @Override @SuppressWarnings("resource") public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request, HttpServletResponse response) throws IOException { // 若不是跨域请求,不处理 // 这个判断极其简单:请求中是否有Origin请求头。有这个头就是跨域请求 if (!CorsUtils.isCorsRequest(request)) { return true; } // response.getHeaders().getAccessControlAllowOrigin() != null // 若响应头里已经设置好了Access-Control-Allow-Origin这个响应头,此处理器也不管了 ServletServerHttpResponse serverResponse = new ServletServerHttpResponse(response); if (responseHasCors(serverResponse)) { logger.trace("Skip: response already contains \"Access-Control-Allow-Origin\""); return true; } // 即使你有Origin请求头,但是是同源的请求,那也不处理 ServletServerHttpRequest serverRequest = new ServletServerHttpRequest(request); if (WebUtils.isSameOrigin(serverRequest)) { logger.trace("Skip: request is from same origin"); return true; } // 是否是预检请求,判断标准如下: // 是跨域请求 && 是`OPTIONS`请求 && 有Access-Control-Request-Method这个请求头 boolean preFlightRequest = CorsUtils.isPreFlightRequest(request); // 若config == null,分两种case: // 是预检请求but木有给config,那就拒绝:给出状态码403 // response.setStatusCode(HttpStatus.FORBIDDEN) // response.getBody().write("Invalid CORS request".getBytes(StandardCharsets.UTF_8)); if (config == null) { if (preFlightRequest) { rejectRequest(serverResponse); return false; // 告诉后面的处理器不用再处理了 } else { // 虽然没给config,但不是预检请求(是真是请求,返回true) return true; } } // 真正的跨域处理逻辑~~~~ // 它的处理逻辑比较简单,立即了W3C规范理解它起来非常简单,本文略 // checkOrigin/checkMethods/checkHeaders等等方法最终都是委托给CorsConfiguration去做的 return handleInternal(serverRequest, serverResponse, config, preFlightRequest); } ... }
使用框架来处理跨域的好处便是:兼容性很强且灵活。它的处理过程如下:
1.若不是跨域请求,不处理(注意是return true后面拦截器还得执行呢)。若是跨域请求继续处理。(是否是跨域请求就看请求头是否有Origin这个头)
2.判断response是否有Access-Control-Allow-Origin这个响应头,若有说明已经被处理过,那本处理器就不再处理了
3.判断是否是同源:即使有Origin请求头,但若是同源的也不处理
4.是否配置了CORS规则,若没有配置:
1. 若是预检请求,直接决绝403,return false
2. 若不是预检请求,则本处理器不处理
5.正常处理CORS请求,大致是如下步骤:
1. 判断 origin 是否合法
2. 判断 method 是否合法
3. 判断 header是否合法
4. 若其中有一项不合法,直接决绝掉403并return false。都合法的话:就在response设置上一些头信息~~~