Spring中的@CrossOrigin注册处理方法源码解析

 更新时间:2023年12月02日 10:54:28   作者:securitit  
这篇文章主要介绍了Spring中的@CrossOrigin注册处理方法源码解析,@CrossOrigin是基于@RequestMapping,@RequestMapping注释方法扫描注册的起点是equestMappingHandlerMapping.afterPropertiesSet(),需要的朋友可以参考下

前言

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

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

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

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

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

@CrossOrigin注释方法扫描注册

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

在这里插入图片描述

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

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注册处理方法源码解析的文章就介绍到这了,更多相关@CrossOrigin注册处理方法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • springboot+mongodb 实现按日期分组分页查询功能

    springboot+mongodb 实现按日期分组分页查询功能

    这篇文章主要介绍了springboot+mongodb 实现按日期分组分页查询功能,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-07-07
  • 如何使用axis调用WebService及Java WebService调用工具类

    如何使用axis调用WebService及Java WebService调用工具类

    Axis是一个基于Java的Web服务框架,可以用来调用Web服务接口,下面这篇文章主要给大家介绍了关于如何使用axis调用WebService及Java WebService调用工具类的相关资料,需要的朋友可以参考下
    2023-04-04
  • Java纯代码实现导出PDF功能

    Java纯代码实现导出PDF功能

    在项目开发中,产品的需求越来越奇葩啦,开始文件下载都是下载为excel的,做着做着需求竟然变了,要求能导出pdf,本文就来和大家分享一下Java实现导出PDF的常用方法吧
    2023-07-07
  • SpringBoot异步调用方法实现场景代码实例

    SpringBoot异步调用方法实现场景代码实例

    这篇文章主要介绍了SpringBoot异步调用方法实现场景代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • Gson中的TypeToken与泛型擦除详情

    Gson中的TypeToken与泛型擦除详情

    这篇文章主要介绍了Gson中的TypeToken与泛型擦除详情,其Gson类提供了toJson()与fromJson()方法,分别用来序列化与反序列化,更多相关内容需要的朋友可以参考一下
    2022-09-09
  • Spring Boot集成Mybatis中如何显示日志的实现

    Spring Boot集成Mybatis中如何显示日志的实现

    这篇文章主要介绍了Spring Boot集成Mybatis中如何显示日志的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-07-07
  • java教程之二个arraylist排序的示例分享

    java教程之二个arraylist排序的示例分享

    常常遇到数组排序的问题,下面提供二个java的arraylist排序示例,需要的朋友可以参考下
    2014-03-03
  • Java实现手写线程池的示例代码

    Java实现手写线程池的示例代码

    在我们的日常的编程当中,并发是始终离不开的主题,而在并发多线程当中,线程池又是一个不可规避的问题。本文就来分享一下如何自己手写一个线程池,需要的可以参考一下
    2022-08-08
  • RabbitMQ的Direct Exchange模式实现的消息发布案例(示例代码)

    RabbitMQ的Direct Exchange模式实现的消息发布案例(示例代码)

    本文介绍了RabbitMQ的DirectExchange模式下的消息发布和消费的实现,详细说明了如何在DirectExchange模式中进行消息的发送和接收,以及消息处理的基本方法,感兴趣的朋友跟随小编一起看看吧
    2024-09-09
  • Java压缩文件工具类ZipUtil使用方法代码示例

    Java压缩文件工具类ZipUtil使用方法代码示例

    这篇文章主要介绍了Java压缩文件工具类ZipUtil使用方法代码示例,具有一定借鉴价值,需要的朋友可以参考下。
    2017-11-11

最新评论