@CrossOrigin源码解析主要分为两个阶段:

  ① @CrossOrigin注释的方法扫描注册。

  ② 请求匹配@CrossOrigin注释的方法。

  本文针对第阶段从源码角度进行解析,关于第阶段请参照《Spring 注解面面通 之 @CrossOrigin 注册处理方法源码解析》。

  请求匹配@CrossOrigin注释的方法

  请求匹配@CrossOrigin注释的方法流程:

  1) DispatcherServlet.doDispatch(...)DispatcherServlet.getHandler(...)AbstractHandlerMapping.getHandler(...)方法。

  标题中所列方法在《Spring 注解面面通 之 @ModelAttribute 深入源码解析》均有介绍,可以查看进行参照,这里不再详细赘述。

  2) AbstractHandlerMethodMapping.getCorsConfiguration(...)方法。

  ① 若处理器为CorsConfigurationSource类型,获取处理器CorsConfiguration配置作为基础配置。

  ② 若处理方法为预处理方法,则返回默认配置CorsConfiguration,其配置均为*

  ③ 若处理方法非预处理方法,根据处理方法查找CorsConfiguration配置,同时与所得配置进行合并。

/*** 获取CorsConfiguration.*/
@Override
protected CorsConfiguration getCorsConfiguration(Object handler, HttpServletRequest request) {// 若处理器为CorsConfigurationSource类型,直接获取处理器CorsConfiguration配置.CorsConfiguration corsConfig = super.getCorsConfiguration(handler, request);if (handler instanceof HandlerMethod) {HandlerMethod handlerMethod = (HandlerMethod) handler;// 请求方法是预处理方法.if (handlerMethod.equals(PREFLIGHT_AMBIGUOUS_MATCH)) {return AbstractHandlerMethodMapping.ALLOW_CORS_CONFIG;}// 请求方法非预处理方法.else {// 根据处理器查找CorsConfiguration配置.CorsConfiguration corsConfigFromMethod = this.mappingRegistry.getCorsConfiguration(handlerMethod);// 合并corsConfig和corsConfigFromMethod.corsConfig = (corsConfig != null ? corsConfig.combine(corsConfigFromMethod) : corsConfigFromMethod);}}return corsConfig;
}

  3) AbstractHandlerMapping.getCorsConfiguration(...)方法。

  若处理器为CorsConfigurationSource类型,获取处理器CorsConfiguration配置作为基础配置。

/*** 检索给定处理程序的CORS配置.* @param handler 处理器.* @param request 当前请求.* @return 处理程序的CORS配置,或者null(如果没有).*/
@Nullable
protected CorsConfiguration getCorsConfiguration(Object handler, HttpServletRequest request) {Object resolvedHandler = handler;if (handler instanceof HandlerExecutionChain) {resolvedHandler = ((HandlerExecutionChain) handler).getHandler();}if (resolvedHandler instanceof CorsConfigurationSource) {return ((CorsConfigurationSource) resolvedHandler).getCorsConfiguration(request);}return null;
}

  4) AbstractHandlerMethodMapping.MappingRegistry.getCorsConfiguration(...)方法。

  ① 从HandlerMethod解析实际的HandlerMethod

  ② 根据HandlerMethodcorsLookup中获取CorsConfiguration配置。

/*** 返回CORS配置.* 线程安全并发使用.*/
public CorsConfiguration getCorsConfiguration(HandlerMethod handlerMethod) {// 解析实际的HandlerMethod.HandlerMethod original = handlerMethod.getResolvedFromHandlerMethod();// 从corsLookup中获取CorsConfiguration配置.return this.corsLookup.get(original != null ? original : handlerMethod);
}

  5) AbstractHandlerMapping.getCorsHandlerExecutionChain(...)方法。

  ① 若请求为预处理请求,AbstractHandlerMapping.PreFlightHandler作为处理器实现。

  ② 若请求非预处理请求,增加拦截器AbstractHandlerMapping.CorsInterceptor对请求进行拦截处理。

/*** 为CORS相关处理更新HandlerExecutionChain.* 对于预处理请求,默认实现用一个简单的HttpRequestHandler替换所选的处理程序,*    该处理程序调用配置的setCorsProcessor.* 对于实际的请求,默认实现插入一个HandlerInterceptor,它进行CORS相关的检查并添加CORS头.* @param request 当前请求.* @param chain 处理链.* @param config 适用的CORS配置(可能是null).*/
protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,HandlerExecutionChain chain, @Nullable CorsConfiguration config) {// 是CORS预处理请求.if (CorsUtils.isPreFlightRequest(request)) {HandlerInterceptor[] interceptors = chain.getInterceptors();chain = new HandlerExecutionChain(new PreFlightHandler(config), interceptors);}// 不是CORS预处理请求.       else {chain.addInterceptor(new CorsInterceptor(config));}return chain;
}

  6) AbstractHandlerMapping.PreFlightHandlerAbstractHandlerMapping.CorsInterceptor类。

  ① 若请求非CORS请求,则跳过处理逻辑。

  ② 若响应已包含Access-Control-Allow-Origin头,则跳过处理逻辑。

  ③ 若请求与服务同源,则跳过处理逻辑。

  ④ 当CORS配置为null时,若请求为预处理请求,则拒绝请求,否则跳过处理逻辑。

/*** 处理请求.*/
@Override
@SuppressWarnings("resource")
public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,HttpServletResponse response) throws IOException {// 非CORS请求,不进行处理.if (!CorsUtils.isCorsRequest(request)) {return true;}// 响应已包含Access-Control-Allow-Origin,不进行处理.ServletServerHttpResponse serverResponse = new ServletServerHttpResponse(response);if (responseHasCors(serverResponse)) {logger.debug("Skip CORS processing: response already contains \"Access-Control-Allow-Origin\" header");return true;}// 请求来自同源,不进行处理.ServletServerHttpRequest serverRequest = new ServletServerHttpRequest(request);if (WebUtils.isSameOrigin(serverRequest)) {logger.debug("Skip CORS processing: request is from same origin");return true;}// 是否预处理请求.boolean preFlightRequest = CorsUtils.isPreFlightRequest(request);if (config == null) {if (preFlightRequest) {rejectRequest(serverResponse);return false;}else {return true;}}// 请求处理方法.return handleInternal(serverRequest, serverResponse, config, preFlightRequest);
}

  ① 获取请求Origin头,检查并获取允许的源。

  ② 针对Vary头,增加值:OriginAccess-Control-Request-MethodAccess-Control-Request-Headers

  ③ 允许的源为空,则拒绝请求。

  ④ 获取请求方法,检查并获取允许的方法。

  ⑤ 允许的方法为空,则拒绝请求。

  ⑥ 获取请求头,检查并获取请求的头。

  ⑦ 若请求为预处理请求,且允许的头为空,拒绝请求。

  ⑧ 设置Access-Control-Allow-OriginAccess-Control-Allow-MethodsAccess-Control-Allow-HeadersAccess-Control-Expose-HeadersAccess-Control-Allow-CredentialsAccess-Control-Max-Age

/*** 请求处理方法.*/
protected boolean handleInternal(ServerHttpRequest request, ServerHttpResponse response,CorsConfiguration config, boolean preFlightRequest) throws IOException {// 获取请求的Origin头.String requestOrigin = request.getHeaders().getOrigin();// 检查并获取允许源.String allowOrigin = checkOrigin(config, requestOrigin);HttpHeaders responseHeaders = response.getHeaders();// 增加Vary头,其值为:Origin、Access-Control-Request-Method、Access-Control-Request-Headers.responseHeaders.addAll(HttpHeaders.VARY, Arrays.asList(HttpHeaders.ORIGIN,HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS));// 允许源为空,则拒绝请求.if (allowOrigin == null) {logger.debug("Rejecting CORS request because '" + requestOrigin + "' origin is not allowed");rejectRequest(response);return false;}// 获取请求方法.HttpMethod requestMethod = getMethodToUse(request, preFlightRequest);// 检查并获取允许的方法.List<HttpMethod> allowMethods = checkMethods(config, requestMethod);// 允许方法为空,则拒绝请求.if (allowMethods == null) {logger.debug("Rejecting CORS request because '" + requestMethod + "' request method is not allowed");rejectRequest(response);return false;}// 获取请求头.List<String> requestHeaders = getHeadersToUse(request, preFlightRequest);// 检查并获取请求的头.List<String> allowHeaders = checkHeaders(config, requestHeaders);// 预处理请求,且允许的头为空,拒绝请求.if (preFlightRequest && allowHeaders == null) {logger.debug("Rejecting CORS request because '" + requestHeaders + "' request headers are not allowed");rejectRequest(response);return false;}// 设置允许的源.responseHeaders.setAccessControlAllowOrigin(allowOrigin);// 设置Access-Control-Allow-Methods.if (preFlightRequest) {responseHeaders.setAccessControlAllowMethods(allowMethods);}// 设置Access-Control-Allow-Headers.if (preFlightRequest && !allowHeaders.isEmpty()) {responseHeaders.setAccessControlAllowHeaders(allowHeaders);}// 设置Access-Control-Expose-Headers.if (!CollectionUtils.isEmpty(config.getExposedHeaders())) {responseHeaders.setAccessControlExposeHeaders(config.getExposedHeaders());}// 设置Access-Control-Allow-Credentials.if (Boolean.TRUE.equals(config.getAllowCredentials())) {responseHeaders.setAccessControlAllowCredentials(true);}// 设置Access-Control-Max-Age.if (preFlightRequest && config.getMaxAge() != null) {responseHeaders.setAccessControlMaxAge(config.getMaxAge());}response.flush();return true;
}

  总结

  只有在了解实现细节的情况下,才能解决那些棘手的问题。随着前后端分离程序变得极为普遍,@CrossOrigin的应用变得尤为重要。

  源码解析基于spring-framework-5.0.5.RELEASE版本源码。

  若文中存在错误和不足,欢迎指正!

Spring 注解面面通 之 @CrossOrigin 处理请求源码解析相关推荐

  1. Spring 注解面面通 之 @CrossOrigin 注册处理方法源码解析

      参照<Spring 注解面面通 之 @RequestMapping 注册处理方法源码解析>,其讲解了@RequestMapping注释的处理方法注册过程,而@CrossOrigin是基 ...

  2. Spring的Autowired自动装配(XML版本+Annotation版本+源码+解析)

    http://moshowgame.iteye.com/blog/1607718 @Autowired自动装配 上面的例子我们用的都是手动装配的,如果DAO-Service一多那就很麻烦了,那么我们需 ...

  3. spring 注解试事物源码解析

    spring 注解试事物源码解析 基于xml注解式事务入口 public class TxNamespaceHandler extends NamespaceHandlerSupport {stati ...

  4. Spring源码深度解析(郝佳)-Spring 常用注解使用及源码解析

      我们在看Spring Boot源码时,经常会看到一些配置类中使用了注解,本身配置类的逻辑就比较复杂了,再加上一些注解在里面,让我们阅读源码更加难解释了,因此,这篇博客主要对配置类上的一些注解的使用 ...

  5. Spring源码深度解析(郝佳)-学习-源码解析-基于注解切面解析(一)

    我们知道,使用面积对象编程(OOP) 有一些弊端,当需要为多个不具有继承关系的对象引入同一个公共的行为时,例如日志,安全检测等,我们只有在每个对象引用公共的行为,这样程序中能产生大量的重复代码,程序就 ...

  6. Spring源码深度解析(郝佳)-学习-源码解析-基于注解注入(二)

    在Spring源码深度解析(郝佳)-学习-源码解析-基于注解bean解析(一)博客中,己经对有注解的类进行了解析,得到了BeanDefinition,但是我们看到属性并没有封装到BeanDefinit ...

  7. Spring源码解析之@Component注解的扫描

    阅读须知 Spring源码版本:4.3.8 文章中使用/* */注释的方法会做深入分析 正文 承接Spring源码解析之context:component-scan标签解析,下面就是扫描的流程: Cl ...

  8. spring MVC cors跨域实现源码解析

    spring MVC cors跨域实现源码解析 名词解释:跨域资源共享(Cross-Origin Resource Sharing) 简单说就是只要协议.IP.http方法任意一个不同就是跨域. sp ...

  9. Spring AOP源码解析-拦截器链的执行过程

    一.简介 在前面的两篇文章中,分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在得到了 bean 的代理对象,且通知也以合适的方式插在了目标方 ...

最新文章

  1. 微生物组—宏基因组分析专题培训开课啦!10月北京
  2. 战略资产配置matlab,资产组合有效前沿的解和最优解(MATLAB语言)
  3. Mysql是否开启binlog日志开启方法
  4. 不使用注解和使用注解的web-service-dao结构
  5. timeSetEvent、回调函数、CCriticalSection
  6. java9 堆外内存_java堆外内存泄漏排查
  7. c语言链表贪吃蛇教程,编《贪吃蛇》最简单的算法,链表法
  8. phpexcel.php实际应用,PHP操作excel的一个例子(原创)-PHP教程,PHP应用
  9. 实验3.3 设计一个用于人事管理的People(人员)类
  10. nginx源码分析:打开监听套接字的流程
  11. OSG仿真案例(9)——JY61陀螺仪控制飞机姿态
  12. Origin申请、安装和激活手记
  13. Office—OneNote快捷键操作
  14. mysql 重置密码_mysql忘记密码如何重置密码,以及修改root密码的三种方法
  15. Xcode 8 size class
  16. WP Engine开发人员工具的好处
  17. 一个IT技术人如果转型做自由职业可以做哪些方向?
  18. 在网上打印双面和单面的资料哪里打印价格便宜
  19. 金蝶二次开发的常见类型
  20. 2021-9-23 base64学习

热门文章

  1. 《QQ西游》,最接近神的一个
  2. 国家精品在线开放课程_开放的互联网安全课程
  3. html单选按钮for,HTML如何实现RadioButton单选按钮
  4. 场效应管理解笔记(N沟道结型场效应管)
  5. 单片机_CT107D训练平台电路原理图\蓝桥杯训练板\输入输出模块\矩阵按键\蜂鸣器电路\继电器电路\LM386功率放大电路,驱动扬声器
  6. php中的run(),PHP daddslashes 使用方法介绍
  7. 打算去上海,不知道会如何?仅仅因为这个理由
  8. 山西被扣车辆成警察坐骑 违章罚款让车主埋单
  9. 建立二叉树并实现层序遍历
  10. ipados怎么把屏幕扩展到电脑