美文网首页
SpringBoot自动配置分析

SpringBoot自动配置分析

作者: 进击的蚂蚁zzzliu | 来源:发表于2021-04-02 14:55 被阅读0次

    一、注解

    @SpringBootApplication 注解

    @SpringBootApplication 注解实际上是一个组合注解,它由三个注解组合而成,分别是 @SpringBootConfiguration、@EnableAutoConfiguration 和 @ComponentScan

    @ComponentScan 注解

    @ComponentScan 注解不是 Spring Boot 引入的新注解,而是属于 Spring 容器管理的内容。@ComponentScan 注解就是扫描基于 @Component 等注解所标注的类所在包下的所有需要注入的类,并把相关 Bean 定义批量加载到容器中。

    @ComponentScan注解的处理逻辑在Configuration解析时的ConfigurationClassParser中进行处理

    #调用栈
    doProcessConfigurationClass:281, ConfigurationClassParser (org.springframework.context.annotation)
    processConfigurationClass:245, ConfigurationClassParser (org.springframework.context.annotation)
    parse:202, ConfigurationClassParser (org.springframework.context.annotation)
    parse:170, ConfigurationClassParser (org.springframework.context.annotation)
    processConfigBeanDefinitions:331, ConfigurationClassPostProcessor (org.springframework.context.annotation)
    postProcessBeanDefinitionRegistry:233, ConfigurationClassPostProcessor (org.springframework.context.annotation)
    invokeBeanDefinitionRegistryPostProcessors:303, PostProcessorRegistrationDelegate (org.springframework.context.support)
    invokeBeanFactoryPostProcessors:106, PostProcessorRegistrationDelegate (org.springframework.context.support)
    invokeBeanFactoryPostProcessors:739, AbstractApplicationContext (org.springframework.context.support)
    refresh:536, AbstractApplicationContext (org.springframework.context.support)
    
    // @ComponentScan注解处理
    protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
                throws IOException {
        ......
        Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
            sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
        if (!componentScans.isEmpty() &&
            !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
            for (AnnotationAttributes componentScan : componentScans) {
                // The config class is annotated with @ComponentScan -> perform the scan immediately
                Set<BeanDefinitionHolder> scannedBeanDefinitions =
                        this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
                // Check the set of scanned definitions for any further config classes and parse recursively if needed
                for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
                    BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
                    if (bdCand == null) {
                        bdCand = holder.getBeanDefinition();
                    }
                    if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
                        parse(bdCand.getBeanClassName(), holder.getBeanName());
                    }
                }
            }
        }
        ......
    }
    

    使用ClassPathBeanDefinitionScanner扫描basePackage下的@Component注解类,递归解析成BeanDefinition并通过 BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry) 添加到 DefaultListableBeanFactory - beanDefinitionMap 中(这个时候bean并没有进行实例化,而是进行了注册。具体的实例化在finishBeanFactoryInitialization方法中执行)。

    @SpringBootConfiguration 注解

    @SpringBootConfiguration 注解比较简单,只是使用了 Spring 中的 @Configuration 注解。@Configuration 注解比较常见,提供了 JavaConfig 配置类实现。具体实现在 ConfigurationClassParser#parse 中,涉及对 @PropertySources、@ComponentScan、@Import、@ImportResource、@Bean、@DeferredImportSelector 注解的处理。

    @EnableAutoConfiguration 注解
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
        String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
        Class<?>[] exclude() default {};
        String[] excludeName() default {};
    }
    

    这里我们关注两个新注解,@AutoConfigurationPackage 和 @Import(AutoConfigurationImportSelector.class)

    @AutoConfigurationPackage 注解

    @AutoConfigurationPackage 注解主要就是引用@Import(AutoConfigurationPackages.Registrar.class)

    @Import 注解
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Import {
        Class<?>[] value();
    }
    

    在 @Import 注解的属性中可以设置需要引入的类名,例如 @AutoConfigurationPackage 注解上的 @Import(AutoConfigurationPackages.Registrar.class)。

    private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
            Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
        if (importCandidates.isEmpty()) {
            return;
        }
        if (checkForCircularImports && isChainedImportOnStack(configClass)) {
            this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
        }
        else {
            this.importStack.push(configClass);
            try {
                for (SourceClass candidate : importCandidates) {
                    if (candidate.isAssignable(ImportSelector.class)) {
                        // Candidate class is an ImportSelector -> delegate to it to determine imports
                        Class<?> candidateClass = candidate.loadClass();
                        ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
                        ParserStrategyUtils.invokeAwareMethods(
                                selector, this.environment, this.resourceLoader, this.registry);
                        if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
                            this.deferredImportSelectors.add(
                                    new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
                        }
                        else {
                            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                            processImports(configClass, currentSourceClass, importSourceClasses, false);
                        }
                    }
                    else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
                        // Candidate class is an ImportBeanDefinitionRegistrar ->
                        // delegate to it to register additional bean definitions
                        Class<?> candidateClass = candidate.loadClass();
                        ImportBeanDefinitionRegistrar registrar =
                                BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
                        ParserStrategyUtils.invokeAwareMethods(
                                registrar, this.environment, this.resourceLoader, this.registry);
                        configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
                    }
                    else {
                        // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
                        // process it as an @Configuration class
                        this.importStack.registerImport(
                                currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
                        processConfigurationClass(candidate.asConfigClass(configClass));
                    }
                }
            }
            catch (BeanDefinitionStoreException ex) {
                throw ex;
            }
            catch (Throwable ex) {
                throw new BeanDefinitionStoreException(
                        "Failed to process import candidates for configuration class [" +
                        configClass.getMetadata().getClassName() + "]", ex);
            }
            finally {
                this.importStack.pop();
            }
        }
    }
    

    根据该类的不同类型,Spring 容器针对 @Import 注解有以下四种处理方式:

    • 如果该类实现了 ImportSelector 接口,调用其 selectImports 方法,返回spring.factories 中配置的类,然后递归调用 processImports 进行处理,最终会被保存在 configurationClasses 中;
    • 如果该类实现了 DeferredImportSelector 接口,则 Spring 容器也会实例化该类并调用其 selectImports方法。DeferredImportSelector 继承了 ImportSelector,区别在于DeferredImportSelector 实例先保存在 deferredImportSelectors 中,要等到 @Configuration 注解中相关的业务全部都处理完了才会通过 processDeferredImportSelectors 调用 selectImports 方法;
    • 如果该类实现了 ImportBeanDefinitionRegistrar 接口,会把Metadata 保存importBeanDefinitionRegistrars 中,在解析完Configuration 后通过 this.reader.loadBeanDefinitions(configClasses),最后在loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars()) 中调用其 registerBeanDefinitions 方法;
    • 如果该类没有实现上述三种接口中的任何一个,把这个类当成是@Configuration注解修饰的类递归重头开始解析这个类;

    关于注解就到这里,下面重点分析下 @Import 的两个类 AutoConfigurationPackages.Registrar 和 AutoConfigurationImportSelector

    二、AutoConfigurationPackages.Registrar

    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    
            @Override
            public void registerBeanDefinitions(AnnotationMetadata metadata,
                    BeanDefinitionRegistry registry) {
                register(registry, new PackageImport(metadata).getPackageName());
            }
     
            @Override
            public Set<Object> determineImports(AnnotationMetadata metadata) {
                return Collections.singleton(new PackageImport(metadata));
            }
    }
    

    可以看到这个 Registrar 类实现了前面第三种情况中提到的 ImportBeanDefinitionRegistrar 接口并重写了 registerBeanDefinitions 方法,该方法中调用 AutoConfigurationPackages 自身的 register 方法:

    public static void register(BeanDefinitionRegistry registry, String... packageNames) {
            if (registry.containsBeanDefinition(BEAN)) {
                BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
                ConstructorArgumentValues constructorArguments = beanDefinition
                        .getConstructorArgumentValues();
                constructorArguments.addIndexedArgumentValue(0,
                        addBasePackages(constructorArguments, packageNames));
            }
            else {
                GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
                beanDefinition.setBeanClass(BasePackages.class);
                beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,
                        packageNames);
                beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
                registry.registerBeanDefinition(BEAN, beanDefinition);
            }
    }
    

    这个方法的逻辑是先判断整个 Bean 有没有被注册,如果已经注册则获取 Bean 的定义,通过 Bean 获取构造函数的参数并添加参数值;如果没有,则创建一个新的 Bean 的定义,设置 Bean 的类型为 AutoConfigurationPackages 类型并进行 Bean 的注册,最终保存在 DefaultListableBeanFactory 的 beanDefinitionMap / beanDefinitionNames中。

    三、AutoConfigurationImportSelector

    AutoConfigurationImportSelector 类实现了 @Import 注解第二种情况中的 DeferredImportSelector 接口,所以会执行如下所示的 selectImports 方法:

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
            if (!isEnabled(annotationMetadata)) {
                return NO_IMPORTS;
            }
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                    .loadMetadata(this.beanClassLoader);
            AnnotationAttributes attributes = getAttributes(annotationMetadata);
     
            //获取 configurations 集合
            List<String> configurations = getCandidateConfigurations(annotationMetadata,
                    attributes);
            configurations = removeDuplicates(configurations);
            Set<String> exclusions = getExclusions(annotationMetadata, attributes);
            checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = filter(configurations, autoConfigurationMetadata);
            fireAutoConfigurationImportEvents(configurations, exclusions);
            return StringUtils.toStringArray(configurations);
    }
    

    这段代码的核心是通过 getCandidateConfigurations 方法获取 configurations 集合并进行过滤。getCandidateConfigurations 方法如下所示:

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
                AnnotationAttributes attributes) {
            List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
                    getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
            Assert.notEmpty(configurations,
                    "No auto configuration classes found in META-INF/spring.factories. If you "
                            + "are using a custom packaging, make sure that file is correct.");
            return configurations;
    }
    

    AutoConfigurationImportSelector 所依赖的最关键组件就是 SpringFactoriesLoader。SpringFactoriesLoader 以服务接口命名的文件是放在 META-INF/spring.factories 文件夹下,对应的 Key 为 EnableAutoConfiguration。SpringFactoriesLoader 会查找所有 META-INF/spring.factories 文件夹中的配置文件,并把 Key 为 EnableAutoConfiguration 所对应的配置项通过反射实例化为配置类并加载到容器中。

    private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
            MultiValueMap<String, String> result = cache.get(classLoader);
            if (result != null) {
                return result;
            }
     
            try {
                Enumeration<URL> urls = (classLoader != null ?
                        classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                        ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
                result = new LinkedMultiValueMap<>();
                while (urls.hasMoreElements()) {
                    URL url = urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    for (Map.Entry<?, ?> entry : properties.entrySet()) {
                        String factoryClassName = ((String) entry.getKey()).trim();
                        for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }
                cache.put(classLoader, result);
                return result;
            }
            catch (IOException ex) {
                throw new IllegalArgumentException("Unable to load factories from location [" +
                        FACTORIES_RESOURCE_LOCATION + "]", ex);
            }
    }
    

    以下就是 spring-boot-autoconfigure 工程中所使用的 spring.factories 配置文件片段,可以看到 EnableAutoConfiguration 项中包含了各式各样的配置项,这些配置项在 Spring Boot 启动过程中都能够通过 SpringFactoriesLoader 加载到运行时环境,从而实现自动化配置:

    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
    org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
    org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
    org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration,\
    org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration,\
    org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
    org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
    org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
    org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
    org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
    …
    

    以上就是 Spring Boot 中基于 @SpringBootApplication 注解实现自动配置的基本过程和原理。当然,@SpringBootApplication 注解也可以基于外部配置文件加载配置信息。基于约定优于配置思想,Spring Boot 在加载外部配置文件的过程中大量使用了默认配置。
    ---------over---------

    相关文章

      网友评论

          本文标题:SpringBoot自动配置分析

          本文链接:https://www.haomeiwen.com/subject/ioufkltx.html