Spring 注解面面通 之 @CrossOrigin 注册处理方法源码解析
参照《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
中,corsLookup
是CORS
应用的关键。
/*** 进行映射注册.*/
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
的有效值为true
或false
。
⑥ 当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
配置的allowedHeaders
、resolvedMethods
属性尚未设置时,则设置所有头均允许。
④ 若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 注册处理方法源码解析相关推荐
- Spring 注解面面通 之 @CrossOrigin 处理请求源码解析
@CrossOrigin源码解析主要分为两个阶段: ① @CrossOrigin注释的方法扫描注册. ② 请求匹配@CrossOrigin注释的方法. 本文针对第②阶段从源码角度进行解 ...
- @Import注解:导入配置类的四种方式源码解析
微信搜索:码农StayUp 主页地址:https://gozhuyinglong.github.io 源码分享:https://github.com/gozhuyinglong/blog-demos ...
- 注册中心 Eureka 源码解析 —— 应用实例注册发现(五)之过期
2019独角兽企业重金招聘Python工程师标准>>> 摘要: 原创出处 http://www.iocoder.cn/Eureka/instance-registry-evict/ ...
- spring boot整合spring5-webflux从0开始的实战及源码解析
上篇文章<你的响应阻塞了没有?--Spring-WebFlux源码分析>介绍了spring5.0 新出来的异步非阻塞服务,很多读者说太理论了,太单调了,这次我们就通过一个从0开始的实例实战 ...
- registerReceiver 动态注册与 sendBroadcast 源码解析
广播的注册分为动态注册和静态注册,静态注册主要在开机后PackageManagerService 利用 AndroidManifest 扫描 安装的apk 获取AndroidManifest内注册的 ...
- Spring源码深度解析(郝佳)-Spring 常用注解使用及源码解析
我们在看Spring Boot源码时,经常会看到一些配置类中使用了注解,本身配置类的逻辑就比较复杂了,再加上一些注解在里面,让我们阅读源码更加难解释了,因此,这篇博客主要对配置类上的一些注解的使用 ...
- Spring源码深度解析(郝佳)-学习-源码解析-基于注解切面解析(一)
我们知道,使用面积对象编程(OOP) 有一些弊端,当需要为多个不具有继承关系的对象引入同一个公共的行为时,例如日志,安全检测等,我们只有在每个对象引用公共的行为,这样程序中能产生大量的重复代码,程序就 ...
- Spring源码深度解析(郝佳)-学习-源码解析-基于注解注入(二)
在Spring源码深度解析(郝佳)-学习-源码解析-基于注解bean解析(一)博客中,己经对有注解的类进行了解析,得到了BeanDefinition,但是我们看到属性并没有封装到BeanDefinit ...
- Spring源码解析之@Component注解的扫描
阅读须知 Spring源码版本:4.3.8 文章中使用/* */注释的方法会做深入分析 正文 承接Spring源码解析之context:component-scan标签解析,下面就是扫描的流程: Cl ...
最新文章
- RDKit支持PostgreSQL配置
- 问卷星调查学生对《算法》教学的建议与反馈
- php如何防止消息被篡改,php如何用libevent处理rabbitmq发来的消息,防止消息丢失或者人为的中断导致消息没有被处理完整...
- 产品经理读:李善友《产品型社群-互联网思维的本质》
- 最常用计算机机箱,电脑机箱的常用材质是什么?
- nsct matlab,图像融合 NSCT算法 matlab
- JavaEE error整理(不断更新)
- python 基础之文件
- Linux下如何发现内存泄漏问题(测试角度)
- Python pip 用法大全
- Drop user 报ORA-00600 [KTSSDRP1]
- 码支付(php版本)应用
- 给java虚拟机增加一个属性,java -D
- 中兴b860刷机运行Linux,整理 B860A 刷机,安装第三方,升降固件,进recovery
- mysql查看锁死的sql,最全指南
- 识读第三角视图(机械识图)
- Excel如何快速隔行插入空行
- mac上使用dbeaver设置字体大小
- java中compare语句的用法,compare的用法_java中 compareTo()的程序代码及用法
- cakephp $this-html-css,CakePHP 使用小技巧