zoukankan      html  css  js  c++  java
  • Spring Boot 自动装配

    自动装配

    环境信息

    • 操作系统: win10

    • jdk: 1.8

    • spring boot: 2.3.0

    前提条件

    • 熟悉 Xml Configuration
    • 熟悉 Java Configuration
    • 熟悉 SPI

    spring 配置信息的历史

    • Xml configuration
    • jdk 5 发布后Spring 提供了 Java Configuration
    • Spring Boot 推出后的 Auto Configuration

    @SpringBootApplication 开始

    // java 本身的注解信息
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    
    // 这个就是 @Configuration 
    @SpringBootConfiguration
    
    // 着重看这个
    @EnableAutoConfiguration
    
    // 这个就是对应的xml的 componentScan
    @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
    public @interface SpringBootApplication {
        
    }
    

    @EnableAutoConfiguration

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    
    // 着重看这两个
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
        
    }
    

    @Import 注解

    • Xml
    • Java Configuration 导入其它的配置信息
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {
        
    }
    

    通过以上的源码追踪片段可以找到两处关键

    • @Import(AutoConfigurationImportSelector.class)
    • @Import(AutoConfigurationPackages.Registrar.class)

    AutoConfigurationImportSelector

    类图

    先创建一个demo,便于后续理解。

    创建一个注解,Import CacheImportSelector

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import({CacheImportSelector.class})
    public @interface EnableDefineService {
        // 配置一些方法
        Class<?>[] exclude() default  {};
    }
    

    创建CacheImportSelector 实现 ImportSelector

    public class CacheImportSelector implements ImportSelector {
    
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            Map<String, Object> attributes = importingClassMetadata
                    .getAnnotationAttributes(EnableDefineService.class.getName());
            // 动态注入bean
            // 返回的是一个固定的CacheService
            return new String[]{CacheService.class.getName()}; 
        }
    }
    

    然后看源码的实现

    但看这一步是不是也非常简单。对比Demo(我只是写死了一个类,源码实现获取类用了一个方法)

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        }
        // 这里是获取配置信息
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
        // 然后转换为数组
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
    

    获取类的配置信息,然后做了一系列检查排除。然后组合成一个返回结果。

    /**
    * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
    * of the importing {@link Configuration @Configuration} class.
    * @param annotationMetadata the annotation metadata of the configuration class
    * @return the auto-configurations that should be imported
    */
    protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);
        // 这一步是写自定义Spring Starter的关键,这里解释 spring.factories 文件解析地方
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = getConfigurationClassFilter().filter(configurations);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }
    

    AutoConfigurationPackages.Registrar

    类图

    先创建一个demo,便于后续理解。

    创建一个注解,Import LoggerDefinitionRegistrar

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import({LoggerDefinitionRegistrar.class})  // 动态注入bean
    public @interface EnableDefineService {
        // 配置一些方法
        Class<?>[] exclude() default  {};
    }
    
    

    实现 ImportBeanDefinitionRegistrar

    public class LoggerDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, 
                                            BeanDefinitionRegistry registry) {
            // 构建了一个 BeanDefinition
            Class beanClass = LoggerService.class;
            RootBeanDefinition beanDefinition = new RootBeanDefinition(beanClass);
            String beanName = StringUtils.uncapitalize(beanClass.getSimpleName());
            
            // 然后使用registry 加入到了Spring IOC容器
            registry.registerBeanDefinition(beanName, beanDefinition);
        }
    }
    

    有了上面一步的剖析,再看源码。

    是不是很简单 Spring Boot也是获取一些包名然后使用 registry 注入Spring IOC容器。

    /**
     * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
     * configuration.
     */
    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, 
                                            BeanDefinitionRegistry registry) {
            register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
        }
    
        @Override
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new PackageImports(metadata));
        }
    
    }
    

    Condition

    这个在Java Config的时候就有了。Auto Config 来了一次扩展。

    补充了 OnClassMissingOnClass等一系列条件。可spring-autoconfigure-metadata.properties或者手动写就能配置的明明白白。

    总结

    在顺着源码看下去。核心点就在于

    • ImportSelector
    • ImportBeanDefinitionRegistrar

    以及其它的集中动态注入Bean的接口。所以看了源码后明白一个道理。

    xml-->Java Config-->Auto Config 的过程中 xml --> Java Config 只是换汤不换药,把xml改成了注解。

    但是 Auto Config 优秀的地方在于它可以把 spring.factoriesspring-autoconfigure-metadata.properties里面的信息+配置文件+condition。就可以把一系列创建bean的操作给安排的明明白白。

    Auto Config 真正优秀的地方在于如果你以前如果自己真正做过需要一个功能模块,然后需要配置N多Bean的苦B开发 你就能明白。

    Auto Config 看似复杂,其实你99%的时间都不用关心这些复杂度。真出奇葩问题在看源码也来得及。

  • 相关阅读:
    java枚举
    [bzoj3436]小K的农场【差分约束系统】【判负环】
    [bzoj1085][SCOI2005]骑士精神【暴力】
    [bzoj1034][ZJOI2008]泡泡堂BNB【贪心】
    [bzoj1046][HAOI2007]上升序列【dp】
    [bzoj1050][HAOI2006]旅行comf【MST】
    [bzoj1047][HAOI2007]理想的正方形【单调队列】
    [bzoj1004][HNOI2008]Cards【群论】
    [bzoj1045][HAOI2008] 糖果传递【构造】
    [bzoj4589]Hard Nim【FWT】
  • 原文地址:https://www.cnblogs.com/linma/p/13037902.html
Copyright © 2011-2022 走看看