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%的时间都不用关心这些复杂度。真出奇葩问题在看源码也来得及。

原文地址:https://www.cnblogs.com/linma/p/13037902.html