java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > @CrossOrigin注册处理方法

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

作者: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注册处理方法内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文