传统配置装载与springboot自动配置装配

装配组件的三种方式:

  • @Component 标记:只适用于装配自己编写的类
  • @Configuration 标记配置类,在配置类中编写用 @Bean 标注的方法:适用于引入的第三方类库和自己编写的类
  • @EnableXXX 与 @Import :适用模块装配,规避了配置分散,维护不灵活的问题

第三种模块装配主要用于解决注册过多,导致编码成本高,维护不灵活的问题(如果只是用@Component、@Configuration、@Bean这三种注解的话,很容易造成这些问题)

如何使用@EnableXXX和@Import

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

    /** 用于导入@Configuration标注的类、ImportSelector实现类、ImportBeanDefinitionRegistrar实现类、普通的类
	 * {@link Configuration @Configuration}, {@link ImportSelector},
	 * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
	 */
    Class<?>[] value();

}
复制代码

从@Import的注释看出,它主要用于导入:

  • @Configuration标注的类
  • ImportSelector实现类
  • ImportBeanDefinitionRegistrar实现类
  • 普通的类

如何编写模块化配置

  • 新建一个 @EnableColorConfig,使用@Import标记,在@Import上指定需要导入的配置

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Import({Red.class, ColorRegistrarConfiguration.class, ColorImportSelector.class, ColorImportBeanDefinitionRegisrar.class})
    public @interface EnableColor {
    
    }
    复制代码

    在这里,Red.class, ColorRegistrarConfiguration.class, ColorImportSelector.class, ColorImportBeanDefinitionRegisrar.class 分别对应着普通类、@Configuration标注的配置类、ImportSelector实现类、ImportBeanDefinitionRegistrar实现类

    public class Red {
    
        private String color;
    
        public String getColor() {
            return color;
        }
    
        public void setColor(String color) {
            this.color = color;
        }
    }
    
    @Configuration
    public class ColorRegistrarConfiguration {
    
        @Bean
        public Yellow yellow() {
            return new Yellow();
        }
    }
    
    
    public class ColorImportSelector implements ImportSelector {
    
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[]{Blue.class.getName(), Green.class.getName()};
        }
    }
    
    public class ColorImportBeanDefinitionRegisrar implements ImportBeanDefinitionRegistrar {
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            registry.registerBeanDefinition("black", new RootBeanDefinition(Black.class));
        }
    }
    复制代码

springboot自动配置装配

对于一个springboot应用来说,我们只需要定义一个启动类即可,如下

// 1. 添加 @SpringBootApplication 注解标记
@SpringBootApplication
public class SpringbootDemoApplication {

    public static void main(String[] args) {
        // 2. 传入主配置类(在springboot内部称为 primarySource )调用 SpringApplication.run 方法
        SpringApplication.run(SpringbootDemoApplication.class, args);
    }

}
复制代码

这样就构造了一个springboot应用。那么它是如何简化掉传统spring应用的各种配置的呢?我们从注解 @SpringBootApplication 入手。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    // 省略代码
}
复制代码

发现这是一个复合注解,逐个来看看

  • SpringBootConfiguration,一个使用 @Configuration 标注的复合注解,用于声明这是一个springboot 应用的配置类

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration
    public @interface SpringBootConfiguration {
    
    }
    复制代码
  • EnableAutoConfiguration,从名字上看,该注解负责启用自动化配置。自身也是一个复合注解

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
        // 省略代码
    }
    复制代码

由于 @EnableAutoConfiguration 本身又由注解 @AutoConfigurationPackage 标注,而且导入了AutoConfigurationImportSelector 配置类。详细下分析它的作用。

保存应用的根路径

首先是 @AutoConfigurationPackage 注解,通过 @Import 导入了ImportBeanDefinitionRegistrar实现类 AutoConfigurationPackages.Registrar

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}
复制代码

AutoConfigurationPackages.Registrar

/**
* 保存包的根路径
* {@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) {
        // 将启动类包名称设置到 BasePackages 类中,并且将 BasePackages 注册到 beanfactory
        register(registry, new PackageImport(metadata).getPackageName());
    }

    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new PackageImport(metadata));
    }

}
复制代码

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);
    }
}
复制代码

BasePackages,对于它来说,自身被注册到 beanfactory 中,并且属性packages保存着应用启动类所在的包路径。

提供了 get 方法来获取这个包路径,方便在后续集成其他第三方组件时可以获取到这个包路径

static final class BasePackages {

    private final List<String> packages;

    private boolean loggedBasePackageInfo;

    BasePackages(String... names) {
        List<String> packages = new ArrayList<>();
        for (String name : names) {
            if (StringUtils.hasText(name)) {
                packages.add(name);
            }
        }
        this.packages = packages;
    }

    public List<String> get() {
        if (!this.loggedBasePackageInfo) {
            if (this.packages.isEmpty()) {
                if (logger.isWarnEnabled()) {
                    logger.warn("@EnableAutoConfiguration was declared on a class "
                                + "in the default package. Automatic @Repository and "
                                + "@Entity scanning is not enabled.");
                }
            }
            else {
                if (logger.isDebugEnabled()) {
                    String packageNames = StringUtils.collectionToCommaDelimitedString(this.packages);
                    logger.debug("@EnableAutoConfiguration was declared on a class " + "in the package '"
                                 + packageNames + "'. Automatic @Repository and @Entity scanning is " + "enabled.");
                }
            }
            this.loggedBasePackageInfo = true;
        }
        return this.packages;
    }

}
复制代码

自动化配置

由 ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry 方法触发 org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#process 加载。

// AutoConfigurationImportSelector.AutoConfigurationGroup#process
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
    Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
                 () -> String.format("Only %s implementations are supported, got %s",
                                     AutoConfigurationImportSelector.class.getSimpleName(),
                                     deferredImportSelector.getClass().getName()));
    AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
        // 加载自动化配置类
        .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
    this.autoConfigurationEntries.add(autoConfigurationEntry);
    for (String importClassName : autoConfigurationEntry.getConfigurations()) {
        this.entries.putIfAbsent(importClassName, annotationMetadata);
    }
}
复制代码

最终会调用到以下方法,经过 SpringFactoriesLoader.loadFactoryNames 方法从类路径中寻找 META-INF/spring.factories 文件,获取到key为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的所有value。

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;
}

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}
复制代码

至于SpringFactoriesLoader的原理,其本质就是使用 ClassLoader 到类路径下加载文件 META-INF/spring.factories,解析为 key-value 的形式。类似于JDK 的 SPI 机制。

原文 

https://juejin.im/post/5efd25056fb9a07e894aa377

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。

PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » 传统配置装载与springboot自动配置装配

赞 (0)
分享到:更多 ()

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址