参照《Spring 注解面面通 之 @RequestMapping 注册处理方法源码解析》,其讲解了@RequestMapping注释的处理方法注册过程,而@CrossOrigin是基于@RequestMapping来应用的。

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

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

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

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

  注意:@CrossOrigin是基于@RequestMapping@RequestMapping注释方法扫描注册的起点是RequestMappingHandlerMapping.afterPropertiesSet()

  @CrossOrigin注释方法扫描注册

  @CrossOrigin注释方法扫描注册流程:

  1) RequestMappingHandlerMapping.afterPropertiesSet()AbstractHandlerMethodMapping.afterPropertiesSet()AbstractHandlerMethodMapping.initHandlerMethods()AbstractHandlerMethodMapping.detectHandlerMethods(...)AbstractHandlerMethodMapping.registerHandlerMethod(...)方法。

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

  2) AbstractHandlerMethodMapping.MappingRegistry.register(...)方法。

  AbstractHandlerMethodMapping.MappingRegistry.register(...)方法里开始初始化CORS的配置,并以方法粒度将其注册到corsLookup中,corsLookupCORS应用的关键。

/*** 进行映射注册.*/
public void register(T mapping, Object handler, Method method) {// 首先,获取写入锁.this.readWriteLock.writeLock().lock();try {// 创建HandlerMethod.HandlerMethod handlerMethod = createHandlerMethod(handler, method);// 验证映射唯一性.assertUniqueMethodMapping(handlerMethod, mapping);if (logger.isInfoEnabled()) {logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod);}this.mappingLookup.put(mapping, handlerMethod);// 搜索直接URL.List<String> directUrls = getDirectUrls(mapping);for (String url : directUrls) {this.urlLookup.add(url, mapping);}// 解析name,并设置映射名称.String name = null;if (getNamingStrategy() != null) {name = getNamingStrategy().getName(handlerMethod, mapping);addMappingName(name, handlerMethod);}// 初始化CORS配置.CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);if (corsConfig != null) {this.corsLookup.put(handlerMethod, corsConfig);}this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));}finally {// 释放写入锁.this.readWriteLock.writeLock().unlock();}
}

  3) RequestMappingHandlerMapping.initCorsConfiguration(...)方法。

  ① 取得当前解析方法所属类的类型。

  ② 在类级别和方法级别分别查找@CrossOrigin注解。

  ③ 若类级别和方法级别不存在@CrossOrigin注解,则跳过此部分处理逻辑。

  ④ 初始化CorsConfiguration配置对象。

  ⑤ 首先更新类级别@CrossOrigin注解信息到CorsConfiguration配置对象,其次更新方法级别@CrossOrigin注解信息到CorsConfiguration配置对象。两者之间是可以简单理解为合集关系,在类级别@CrossOrigin注解信息基础上,填加方法级别@CrossOrigin注解信息。

  ⑥ 若@CrossOrigin注解未标注允许的HTTP方法,则以@RequestMapping注解标注的HTTP方法作为允许的HTTP方法。

  ⑦ 调用config.applyPermitDefaultValues()为未初始化的配置设置默认值。

/*** 初始化CORS配置.*/
@Override
protected CorsConfiguration initCorsConfiguration(Object handler, Method method, RequestMappingInfo mappingInfo) {// 创建HandlerMethod.HandlerMethod handlerMethod = createHandlerMethod(handler, method);// 获取方法所属类型.Class<?> beanType = handlerMethod.getBeanType();// 查找类级别注释.CrossOrigin typeAnnotation = AnnotatedElementUtils.findMergedAnnotation(beanType, CrossOrigin.class);// 查找方法级别注释.CrossOrigin methodAnnotation = AnnotatedElementUtils.findMergedAnnotation(method, CrossOrigin.class);// 类和方法级别无注释,跳过处理.if (typeAnnotation == null && methodAnnotation == null) {return null;}// 初始化配置.CorsConfiguration config = new CorsConfiguration();// 更新类级别@CrossOrigin注解配置.updateCorsConfig(config, typeAnnotation);// 更新方法级别@CrossOrigin注解配置.    updateCorsConfig(config, methodAnnotation);// 若@CrossOrigin未标准HTTP方法,则以@RequestMapping标准HTTP方法为准.if (CollectionUtils.isEmpty(config.getAllowedMethods())) {for (RequestMethod allowedMethod : mappingInfo.getMethodsCondition().getMethods()) {config.addAllowedMethod(allowedMethod.name());}}// 为未初始化的配置设置默认值.return config.applyPermitDefaultValues();
}

  4) RequestMappingHandlerMapping.updateCorsConfig(...)方法。

  ① 解析@CrossOrigin注解的origins属性到CorsConfiguration配置。

​  ② 解析@CrossOrigin注解的methods属性到CorsConfiguration配置。

  ③ 解析@CrossOrigin注解的allowedHeaders属性到CorsConfiguration配置。

  ④ 解析@CrossOrigin注解的exposedHeaders属性到CorsConfiguration配置。

  ⑤ 解析@CrossOrigin注解的allowCredentials属性到CorsConfiguration配置。allowCredentials的有效值为truefalse

  ⑥ 当CorsConfiguration配置未设置maxAge@CrossOrigin注解的maxAge属性为大于0的有效值时,解析@CrossOrigin注解的maxAge属性到CorsConfiguration配置。

/*** 更新CORS配置.*/
private void updateCorsConfig(CorsConfiguration config, @Nullable CrossOrigin annotation) {// 注解为空,跳过处理.if (annotation == null) {return;}// 解析@CrossOrigin.origins属性到配置.for (String origin : annotation.origins()) {config.addAllowedOrigin(resolveCorsAnnotationValue(origin));}// 解析@CrossOrigin.methods属性到配置.for (RequestMethod method : annotation.methods()) {config.addAllowedMethod(method.name());}// 解析@CrossOrigin.allowedHeaders属性到配置.for (String header : annotation.allowedHeaders()) {config.addAllowedHeader(resolveCorsAnnotationValue(header));}// 解析@CrossOrigin.exposedHeaders属性到配置.for (String header : annotation.exposedHeaders()) {config.addExposedHeader(resolveCorsAnnotationValue(header));}// 解析@CrossOrigin.allowCredentials属性到配置.String allowCredentials = resolveCorsAnnotationValue(annotation.allowCredentials());if ("true".equalsIgnoreCase(allowCredentials)) {config.setAllowCredentials(true);}else if ("false".equalsIgnoreCase(allowCredentials)) {config.setAllowCredentials(false);}else if (!allowCredentials.isEmpty()) {throw new IllegalStateException("@CrossOrigin's allowCredentials value must be \"true\", \"false\", " +"or an empty string (\"\"): current value is [" + allowCredentials + "]");}// 解析@CrossOrigin.maxAge属性到配置.if (annotation.maxAge() >= 0 && config.getMaxAge() == null) {config.setMaxAge(annotation.maxAge());}
}

  5) CorsConfiguration.applyPermitDefaultValues()方法。

​  ① 若CorsConfiguration配置的allowedOrigins属性尚未设置时,则设置所有源均允许。

  ② 若CorsConfiguration配置的allowedMethods属性尚未设置时,则设置所有源均允许。

  ③ 若CorsConfiguration配置的allowedHeadersresolvedMethods属性尚未设置时,则设置所有头均允许。

​  ④ 若CorsConfiguration配置的maxAge属性尚未设置时,则设置为1800秒(30分钟)。

/*** 默认情况下,新建的CorsConfiguration不允许任何跨源请求,必须填加相应配置以允许请求.* * 使用这个方法为未初始化的配置打开默认的跨域设置,包括:GET、HEAD、POST.* 但是请注意,此方法不会覆盖任何已设置的现有值.** 如果尚未设置,则应用以下默认值:*  允许所有源.* 允许简单HTTP方法:GET、HEAD、POST.*  允许所有HTTP头.*  设置最大使用时间1800秒(30分钟).*/
public CorsConfiguration applyPermitDefaultValues() {// 若未设置允许源,则设置所有源均允许.if (this.allowedOrigins == null) {this.allowedOrigins = DEFAULT_PERMIT_ALL;}// 若未设置允许方法,则设置允许GET、HEAD、POST.if (this.allowedMethods == null) {this.allowedMethods = DEFAULT_PERMIT_METHODS;this.resolvedMethods = DEFAULT_PERMIT_METHODS.stream().map(HttpMethod::resolve).collect(Collectors.toList());}// 若未设置允许头,则设置所有头均允许.if (this.allowedHeaders == null) {this.allowedHeaders = DEFAULT_PERMIT_ALL;}// 若未设置最大使用时间,则设置为1800秒(30分钟).if (this.maxAge == null) {this.maxAge = 1800L;}return this;
}

  总结

  正如文中所说,@CrossOrigin解析的目的,即是将解析后的配置注册到AbstractHandlerMethodMapping.MappingRegistry.corsLookup属性中,以便webmvc模块处理请求使用。

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

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

Spring 注解面面通 之 @CrossOrigin 注册处理方法源码解析相关推荐

  1. Spring 注解面面通 之 @CrossOrigin 处理请求源码解析

      @CrossOrigin源码解析主要分为两个阶段:   ① @CrossOrigin注释的方法扫描注册.   ② 请求匹配@CrossOrigin注释的方法.   本文针对第②阶段从源码角度进行解 ...

  2. @Import注解:导入配置类的四种方式源码解析

    微信搜索:码农StayUp 主页地址:https://gozhuyinglong.github.io 源码分享:https://github.com/gozhuyinglong/blog-demos ...

  3. 注册中心 Eureka 源码解析 —— 应用实例注册发现(五)之过期

    2019独角兽企业重金招聘Python工程师标准>>> 摘要: 原创出处 http://www.iocoder.cn/Eureka/instance-registry-evict/ ...

  4. spring boot整合spring5-webflux从0开始的实战及源码解析

    上篇文章<你的响应阻塞了没有?--Spring-WebFlux源码分析>介绍了spring5.0 新出来的异步非阻塞服务,很多读者说太理论了,太单调了,这次我们就通过一个从0开始的实例实战 ...

  5. registerReceiver 动态注册与 sendBroadcast 源码解析

    广播的注册分为动态注册和静态注册,静态注册主要在开机后PackageManagerService 利用 AndroidManifest 扫描 安装的apk 获取AndroidManifest内注册的 ...

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

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

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

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

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

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

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

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

最新文章

  1. RDKit支持PostgreSQL配置
  2. 问卷星调查学生对《算法》教学的建议与反馈
  3. php如何防止消息被篡改,php如何用libevent处理rabbitmq发来的消息,防止消息丢失或者人为的中断导致消息没有被处理完整...
  4. 产品经理读:李善友《产品型社群-互联网思维的本质》
  5. 最常用计算机机箱,电脑机箱的常用材质是什么?
  6. nsct matlab,图像融合 NSCT算法 matlab
  7. JavaEE error整理(不断更新)
  8. python 基础之文件
  9. Linux下如何发现内存泄漏问题(测试角度)
  10. Python pip 用法大全
  11. Drop user 报ORA-00600 [KTSSDRP1]
  12. 码支付(php版本)应用
  13. 给java虚拟机增加一个属性,java -D
  14. 中兴b860刷机运行Linux,整理 B860A 刷机,安装第三方,升降固件,进recovery
  15. mysql查看锁死的sql,最全指南
  16. 识读第三角视图(机械识图)
  17. Excel如何快速隔行插入空行
  18. mac上使用dbeaver设置字体大小
  19. java中compare语句的用法,compare的用法_java中 compareTo()的程序代码及用法
  20. cakephp $this-html-css,CakePHP 使用小技巧

热门文章

  1. python加密-sha256-md5
  2. 某资产管理公司部署贷前征信调查报表系统实录
  3. 拓嘉辰丰:拼多多要快速获取流量有哪些方法
  4. Linux网络包的收发流程
  5. N沟通场效应管深度图解(1)工作原理及Multisim实例仿真
  6. 如何选择架构中的底层工具?OpenMLDB 在 Akulaku 数据驱动中的应用实践给你答案
  7. 高层论坛︱王家耀院士:“互联网+”时空大数据与智慧城市(3)
  8. 移动机器人——运动模型
  9. C语言 转义字符和ASCII码对照表
  10. 怎样测试电脑电源好坏