取法其上,得乎其中

从Bean加载机制到SpringBoot自动装配机制的源码学习

概述

所谓自动装配,其实就是SpringBoot提供的一种让第三方组件快速集成到Boot里面的一种机制。依然是“约定大于配置”的一种思想体现,实际上在大多数的业务开发中很多配置往往都是固定的,少数的一些是需要专门配置的。那么第三方组件设置好默认的配置,额外的就由用户自己配置作以替换就是咯。

模块装配

假设现在有一个场景,使用代码模拟构建一个酒吧,酒吧里有吧台、调酒师、服务员、老板,四种不同的实体。酒馆可看做ApplicationContext,然后我们要通过模块装配的方式用不同的方法将这几个实体导入到Context之中。

首先我们要先设置一个注解@EnableTavern,当它被标注的时候,我们可以认为这个时候就需要把对应实体注册出来了。实际上在我们日常开发引入第三方组件的时候经常会遇到@EnableXXX的注解,它们的文档会说:“你要新建一个配置类,然后在上面去标注这个注解,这样我们的框架就可以正确的加载进去啦”,当然也有标注在启动类上的,是因为启动类的注解@SpringBootApplication标注了@SpringBootConfiguration注解,然后这个注解就已经标注了@Configuration。差点绕晕...实际上启动类也是一个配置类。

于是有了:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({Boss.class})
public @interface EnableTavern {
}

以及:

@Configuration
@EnableTavern
public class TavernConfiguration {
}

与平时的注解不同,可以发现我使用了@Import注解,从语义上猜就是把Boss类导入了进来,那么从这个注解标注的层次去想,大概就是如果我标注了这个注解,那么就代表Boss类会自动的加载到IOC容器??

是的,确实是这样,同时如果在@Import注解的参数中再加上一个配置类,如下

@Import({Boss.class,BartenderConfiguration.class})
public @interface EnableTavern {
}
@Configuration
public class BartenderConfiguration {
    @Bean
    public Bartender zhangsan(){
        return new Bartender("张三");
    }
}

也可以通过这种方式去加载到IOC容器之中。但是,这样做的前提是这个配置类一定是要在启动类所在的包之中的,但是我们平常用的第三方组件的包不可能跟启动类这边的包一样啊?

那么问题就出来了:SpringBoot怎么知道哪个包下有对应的配置类呢,这就引入了ImportSelector。

ImportSelector

我们来通过ImportSelector去导入“吧台”对应的配置类。

public class Bar {
}

public class BarImportSelector implements ImportSelector {    
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//        return new String[]{BarConfiguration.class.getName()};
        return new String[]{"com.example.BarConfiguration"};
    }
}

如以上代码所示,我们创建了一个叫“BarImportSelector”的类,并且继承了“ImportSelector”以及实现了selectImports方法,根据样例就可以得知:这个方法返回了一个字符串数组,其中元素存放了BarConfiguration的全类名。

然后再@EnableTavern中更新@Import:

@Import({Boss.class,BartenderConfiguration.class,BarImportSelector.class})
public @interface EnableTavern {
}

于是SpringBoot就会根据上面设置的全类名一样把“吧台”对应的配置类给解析到了容器之中。以此延伸去想一下...如果在selectImports方法中我们做的工作是解析资源目录下的“spring.factories”呢?是的,如果第三方库在资源目录下把自己所需要的配置类写到“spring.factories”里的话,也就是实现了自动装配!

ImportBeanDefinitionRegistrar

除了基础的ImportSelector方式,ImportBeanDefinitionRegistrar也是一种注入Bean的一种方法,其区别在于后者可以在运行的时候灵活的去直接注册BeanDefinition。

public class WaiterRegistrar implements ImportBeanDefinitionRegistrar{
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        registry.registerBeanDefinition("waiter", new RootBeanDefinition(Waiter.class));
    }
}

同样的,它需要在@EnableTavern的@Import注解中去声明。对于这个BeanDefinition相关的信息暂且不表,此处只是简短的记录这样一种编程式的注入方式。

DeferredImportSelector

DeferredImportSelector是ImportSelector是的一个子类,其方法的设计也是相同。两个方法的主要区别在于其执行时间,如果是ImportSelector的话,其执行时间在容器解析各个配置类,但并没有解析@Bean的期间。而后者是配置类已经完全解析了。

因此两者的主要区别在于后者在执行的时候可以了解到容器加载完毕后的上下文,那么依此就可以实现条件判断,它可以根据环境状态去判断要对哪些配置类进行加载,这也就是“@ConditionalOnXXX”的实现原理。

从@SpringBootApplication开始

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
        excludeFilters = {@ComponentScan.Filter(
                type = FilterType.CUSTOM,
                classes = {TypeExcludeFilter.class}
        ), @ComponentScan.Filter(
                type = FilterType.CUSTOM,
                classes = {AutoConfigurationExcludeFilter.class}
        )}
)
public @interface SpringBootApplication

从这个注解所标注的三个注解来逐个分析。

@ComponentScan

@ComponentScan(
        excludeFilters = {@ComponentScan.Filter(
                type = FilterType.CUSTOM,
                classes = {TypeExcludeFilter.class}
        ), @ComponentScan.Filter(
                type = FilterType.CUSTOM,
                classes = {AutoConfigurationExcludeFilter.class}
        )}
)

从这个注解本身来讲,它的主要作用是标志要对当前类(启动类)所在的包下进行组件的扫描,但是在其中又通过“excludeFilters”参数配置了两个过滤器。注意,“excludeFilters”表示的是排除过滤器有哪些,而不是要排除那个过滤器。这里配置的语义是,要配置两个类别为“排除过滤器”的过滤器,而不是排除掉两个过滤器。

TypeExcludeFilter

TypeExcludeFilter的主要作用体现于你可以去继承它并重写match方法以创建过滤器,然后你可以根据你的规则去判定某个Bean是否允许被加载。

而TypeExcludeFilter类本身实现的match方法主要是处理所有继承于它的过滤器,去调用它们的match方法来看是否匹配,如果某个过滤器的match方法返回为true,即这个Bean不允许被加载。

下面为TypeExcludeFilter类本身的match方法实现:

    @Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
        throws IOException {
    if (this.beanFactory instanceof ListableBeanFactory && getClass() == TypeExcludeFilter.class) {
                    // getDelegates() 方法从容器中获取所有的TypeExcludeFilter类型的过滤器
        for (TypeExcludeFilter delegate : getDelegates()) {
            if (delegate.match(metadataReader, metadataReaderFactory)) {
                return true;
            }
        }
    }
    return false;
}
    private Collection<TypeExcludeFilter> getDelegates() {
    Collection<TypeExcludeFilter> delegates = this.delegates;
    if (delegates == null) {
        delegates = ((ListableBeanFactory) this.beanFactory).getBeansOfType(TypeExcludeFilter.class).values();
        this.delegates = delegates;
    }
    return delegates;
}

AutoConfigurationExcludeFilter

这个过滤器的语义比较明显,match为true条件是:一个类是配置类且这个类是自动配置类。而具体的逻辑牵扯到其他地方,所以仅需了解通过这个过滤器可以把“自动配置类”给匹配出来。

public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
    // 是配置类 且 是自动配置类
    return this.isConfiguration(metadataReader) && this.isAutoConfiguration(metadataReader);
}

private boolean isConfiguration(MetadataReader metadataReader) {
    // 被 @Configuration 注解标注
    return metadataReader.getAnnotationMetadata().isAnnotated(Configuration.class.getName());
}

private boolean isAutoConfiguration(MetadataReader metadataReader) {
    boolean annotatedWithAutoConfiguration = metadataReader.getAnnotationMetadata().isAnnotated(AutoConfiguration.class.getName());
    // 要么被 @AutoConfiguration 注解标注,要么在 getAutoConfigurations() 返回的List之中。
    return annotatedWithAutoConfiguration || this.getAutoConfigurations().contains(metadataReader.getClassMetadata().getClassName());
}

其中getAutoConfigurations()返回的列表是通过解析spring.factories文件得来的列表。

@SpringBootConfiguration

实际上里面是一个@Configuration,标注它是一个配置类,且是Spring主体的配置类。

@EnableAutoConfiguration

这个注解就标志着要启用自动配置功能,即通过导入的依赖、上下文配置去合理的加载默认的自动配置。从功能角度去想,是不是涉及:扫描配置类,判断是否加载配置类?

@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration

@AutoConfigurationPackage

主要目的是标识启动类的所在包。

@Import({AutoConfigurationPackages.Registrar.class})
public @interface AutoConfigurationPackage

同时,如果标注这个注解也就意味着通过@Import引入了一个Registrar类,它的主要作用是将根目录的地址记录下来。

AutoConfigurationImportSelector

从名字可以得知,这是一个ImportSelector,并且实现了DeferredImportSelector类,通过一开始所叙述的相关内容就可以理解这个类是做什么的了。

而这个类对功能的主要实现是通过getImportGroup()方法返回的AutoConfigurationGroup类中的process方法。按照之前对于DeferredImportSelector的理解,对应的方法应该是selectImports(),而不是getImportGroup()。

这里主要涉及到DeferredImportSelector类的一个分组概念,即通过一种方式对不同的DeferredImportSelector加以区分,而通过getImportGroup()方法可以获得对应组的实例。概念有些紊乱,但实际上在SpringBoot中并没有过多的涉及到这个概念,因此只需要把重心放在getImportGroup()方法返回的AutoConfigurationGroup类中的process方法。

@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
    // ... 省略断言
    // 通过 getAutoConfigurationEntry() 方法去获取 AutoConfigurationEntry 对象,
    // 但实际上这个对象就是包装了允许加载的配置类和被排除的配置类
    AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
        .getAutoConfigurationEntry(annotationMetadata);
    this.autoConfigurationEntries.add(autoConfigurationEntry);
    for (String importClassName : autoConfigurationEntry.getConfigurations()) {
        // 存放
        this.entries.putIfAbsent(importClassName, annotationMetadata);
    }
}

主要的逻辑在getAutoConfigurationEntry方法,其中主要步骤实际上就是通过SPI机制去加载所有的配置类,然后移去不满足条件的配置类,再之后封装成Entry对象。

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    // 加载注解配置
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 加载所有候选的自动配置类
    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);
    // 封装成Entry
    return new AutoConfigurationEntry(configurations, exclusions);
}

主要是通过Spring SPI机制加载了所有配置了@EnableAutoConfiguration的类。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = new ArrayList<>(
            SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
    ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
    return configurations;
}

最后

如此这样,加载流程便结束了。

还需要深究的是:

  1. Spring SPI机制究竟如何扫描的"spring.factories"文件

参考书籍:

  1. 《Spring Boot源码解读与原理剖析》 作者: LinkedBear
从Bean加载机制到SpringBoot自动装配机制的源码学习

https://ku-m.cn/index.php/archives/788/

作者

KuM

发布时间

2023-11-17

许可协议

CC BY 4.0

添加新评论