百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

「Spring Boot 源码研究 」- 条件化配置过滤器的实现剖析

toyiye 2024-06-21 12:37 15 浏览 0 评论



1. AutoConfigurationImportFilter的作用

  1. 之前讲解了SpringBootCondition自动化条件配置,我们分析了内部是如何具体实现,在整个实现当中, 还有一个很重要的接口, AutoConfigurationImportFilter是它的前置调用, 它是一个过滤器接口,我们再做深入研究, 看下是如何控制处理这么多条件注解, 又是怎样过滤处理的,从性能效率又做了哪些处理?
  2. AutoConfigurationImportFilter的源码:
@FunctionalInterface
public interface AutoConfigurationImportFilter {

	/**
	 * Apply the filter to the given auto-configuration class candidates.
	 * @param autoConfigurationClasses the auto-configuration classes being considered.
	 * This array may contain {@code null} elements. Implementations should not change the
	 * values in this array.
	 * @param autoConfigurationMetadata access to the meta-data generated by the
	 * auto-configure annotation processor
	 * @return a boolean array indicating which of the auto-configuration classes should
	 * be imported. The returned array must be the same size as the incoming
	 * {@code autoConfigurationClasses} parameter. Entries containing {@code false} will
	 * not be imported.
	 */
	boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata);

}
  • 从说明可以看到,该类主要功能是过滤那些在spring.factories配置文件中定义的自动化配置项, 还有一个重要作用是在自动化配置类的字节码加载之前进行拦截过滤,提升处理效率, 节省资源开销。
  • 2. AutoConfigurationImportFilter UML类图说明

    从图中可以看到, AutoConfigurationImportFilter一共有三个实现类(OnBeanCondition、OnClasssCondition、OnWebApplicationCondition),三个类都是通过FilteringSpringBootCondition抽象父类间接实现,AutoConfigurationImportFilter在所有OnXXXCondition条件注解类的上层,这样大概就能看出它们的调用栈的关联关系, 经过研究代码, Spring Boot 会先调用AutoConfigurationImportFilter的match方法做过滤处理, 后面再通过loadBeanDefinitions触发Condition的matches方法做条件判断。

    3. FilteringSpringBootCondition抽象类

    FilteringSpringBootCondition是一个抽象类, 它继承SpringBootCondition,实现AutoConfigurationImportFilter的match接口, 内部调用抽象方法getOutcomes负责具体的过滤逻辑处理。

    abstract class FilteringSpringBootCondition extends SpringBootCondition
            implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {
    
        // bean 工厂
        private BeanFactory beanFactory;
        // bean 加载器
        private ClassLoader beanClassLoader;
    
        // 
        @Override
        public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
            // 获取条件化判断报告, 用于记录处理结果
            ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
            // 获取具体匹配处理结果, 由抽象方法getOutcomes负责具体实现
            ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
            boolean[] match = new boolean[outcomes.length];
            // 遍历条件处理结果
            for (int i = 0; i < outcomes.length; i++) {
                match[i] = (outcomes[i] == null || outcomes[i].isMatch());
                if (!match[i] && outcomes[i] != null) {
                    // 日志打印记录
                    logOutcome(autoConfigurationClasses[i], outcomes[i]);
                    if (report != null) {
                        // 记录匹配处理结果
                        report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
                    }
                }
            }
            return match;
        }
        
        ...
    }
        
    • 先获取ConditionEvaluationReport对象, 用于记录处理结果。
    • 调用getOutcomes方法, 这个一个抽象方法, 返回匹配处理结果, 由上面UML图中的OnXXXCondition等类负责具体实现。
    • 接下来创建match数组, 布尔值标记处理结果。
    • 下面还会调用logOutcome方法, 做日志打印处理。调用recordConditionEvaluation, 记录匹配结果。

    除了match方法, FilteringSpringBootCondition下还有个 filter 方法。

    Filter方法, protected修饰, 实际上会由OnXXXCondition的getOutcomes方法调用, 从UML关系图可以看到, 实际是由子类处理逻辑实现过程中调用。

    protected List<String> filter(Collection<String> classNames, ClassNameFilter classNameFilter,
                ClassLoader classLoader) {
            // 校验, 为空判断
            if (CollectionUtils.isEmpty(classNames)) {
                return Collections.emptyList();
            }
            // 记录match匹配结果
            List<String> matches = new ArrayList<>(classNames.size());
            // 遍历处理
            for (String candidate : classNames) {
                // 从指定的classLoader中加载class,再根据ClassNameFilter类型, 返回最终结果
                if (classNameFilter.matches(candidate, classLoader)) {
                    matches.add(candidate);
                }
            }
            return matches;
        }
    

    从源码可以看到,先做简单的为空判断, 具体则是通过classNameFilter的match方法做处理。 我们再看下ClassNameFilter的源码:

    protected enum ClassNameFilter {
            // 两种类型, 当前存在优先, 如果classLoader中能够加载指定类, 返回true
            PRESENT {
    
                @Override
                public boolean matches(String className, ClassLoader classLoader) {
                    return isPresent(className, classLoader);
                }
    
            },
            // 缺失优先规则, 即便在classLoader中能够加载指定类, 也是返回false
            MISSING {
    
                @Override
                public boolean matches(String className, ClassLoader classLoader) {
                    return !isPresent(className, classLoader);
                }
    
            };
            
            // 抽象方法, 有子类负责具体匹配逻辑实现 
            public abstract boolean matches(String className, ClassLoader classLoader);
            
            // 判断指定的类, 是否能够通过指定的classLoader加载
            public static boolean isPresent(String className, ClassLoader classLoader) {
                if (classLoader == null) {
                    classLoader = ClassUtils.getDefaultClassLoader();
                }
                try {
                    forName(className, classLoader);
                    return true;
                }
                catch (Throwable ex) {
                    return false;
                }
            }
        
            // 类的加载处理
            private static Class<?> forName(String className, ClassLoader classLoader) throws ClassNotFoundException {
                if (classLoader != null) {
                    return classLoader.loadClass(className);
                }
                return Class.forName(className);
            }
    
        }

    从中可以看出, 这里面有两种形式判断,一种是PRESENT, 另外一种是MISSING, 两种类型为相反逻辑, 通过isPresent方法做判断,里面则根据ClassLoader, 如果不为空, 则加载目标CLASS,处理没有报错, 则返回true值。

    讲到这里, Filter的作用是什么?ClassNameFilter两种类型有什么意义? 我们举个例子说明, @ConditionalOnClass和@ConditionalOnMissingClass两个注解,判断条件是指定的CLASS是否存在。 如果采用ConditionalOnClass注解, 那么采用PRESENT存在优先规则, 如果采用ConditionalOnMissingClass注解, 那么采用MISSING缺失优先规则。

    4. AutoConfigurationImportSelector类

    再分析一下AutoConfigurationImportSelector这个类, 这是一个自动配置导入选择处理器,在AutoConfigurationImportFilter的match方法之前调用, 是属于上层调用。先由springFactores加载选择处理器, 主要包含OnClassCondition、OnWebApplicationCondition和OnBeanCondition等, 再做具体的条件判断处理。 我们了解下它的处理逻辑:

    public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
            ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
                
    private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
            long startTime = System.nanoTime();
            // 根据配置上下文, 获取所有需要处理的自动化配置类信息, 也就是所有的auotconfigration实现类
            String[] candidates = StringUtils.toStringArray(configurations);
            boolean[] skip = new boolean[candidates.length];
            boolean skipped = false;
            // 遍历处理, 通过getAutoConfigurationImportFilters方法, 获取springFactores中的选择处理器, 包含OnClassCondition、OnWebApplicationCondition和OnBeanCondition。
            for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
                // 填充filter信息
                invokeAwareMethods(filter);
                // 获取filter的匹配结果
                boolean[] match = filter.match(candidates, autoConfigurationMetadata);
                for (int i = 0; i < match.length; i++) {
                    if (!match[i]) {
                        // 如果没有匹配, skip标记为true
                        skip[i] = true;
                        // 清除该auotconfigration记录信息
                        candidates[i] = null;
                        skipped = true;
                    }
                }
            }
            if (!skipped) {
                // 完全匹配, 直接返回configurations数据
                return configurations;
            }
            List<String> result = new ArrayList<>(candidates.length);
            for (int i = 0; i < candidates.length; i++) {
                if (!skip[i]) {
                    // 记录需要处理的自动化配置信息
                    result.add(candidates[i]);
                }
            }
            if (logger.isTraceEnabled()) {
                // 是否需要日志打印追踪
                int numberFiltered = configurations.size() - result.size();
                logger.trace("Filtered " + numberFiltered + " auto configuration class in "
                        + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
            }
            return new ArrayList<>(result);
        }
        ...
    }

    可以看到, 通过getAutoConfigurationImportFilters()加载过滤器, 在调用过滤器的match执行逻辑处理。条件匹配处理完成之后, 如果完全匹配, 则直接返回Configuration信息, 否则, 记录需要处理的自动化配置信息并做返回。 Configuration信息实际就是Spring Boot内置的一百多个自动化配置类:

    这里也就是根据条件去过滤判断, 哪些AutoConfiguration符合规则, 哪些不符合规则, 只有符合规则的自动化配置类才会进入加载流程,实现对应的组件功能。

    5. 总结

    AutoConfigurationImportFilter是Spring Boot条件化注解的核心过滤器接口,这个类在启动的时候通过SPI机制实现,在Spring Boot的条件化配置中会进行回调. 基于Conditional的自动化配置主要流程就分析到这里, 细节上就不再赘述, 大家有空可以再跟踪源码深入研究,了解更为细节的自动化配置的处理逻辑。



    相关推荐

    为何越来越多的编程语言使用JSON(为什么编程)

    JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

    何时在数据库中使用 JSON(数据库用json格式存储)

    在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

    MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

    前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

    JSON对象花样进阶(json格式对象)

    一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

    深入理解 JSON 和 Form-data(json和formdata提交区别)

    在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

    JSON 语法(json 语法 priority)

    JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

    JSON语法详解(json的语法规则)

    JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

    MySQL JSON数据类型操作(mysql的json)

    概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

    JSON的数据模式(json数据格式示例)

    像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

    前端学习——JSON格式详解(后端json格式)

    JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

    什么是 JSON:详解 JSON 及其优势(什么叫json)

    现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

    PostgreSQL JSON 类型:处理结构化数据

    PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

    JavaScript:JSON、三种包装类(javascript 包)

    JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

    Python数据分析 只要1分钟 教你玩转JSON 全程干货

    Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

    比较一下JSON与XML两种数据格式?(json和xml哪个好)

    JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

    取消回复欢迎 发表评论:

    请填写验证码