SpringBoot 02:源码分析——自动装配


SpringBoot 02:源码分析——自动装配

SpringBoot自动配置根据我们添加的jar依赖项自动配置SpringBoot应用程序。

SpringBootApplication注解分析

例如,如果类路径中存在H2数据库Jar,而我们尚未手动配置任何与数据库相关的bean,则Spring Boot的自动配置功能会在项目中自动对其进行配置。

我们可以启用自动通过使用注解 @EnableAutoConfiguration 配置功能。但是此注解不使用,因为它包装在 @SpringBootApplication 注解内。注解**@SpringBootApplication**是三个注解的组合: @ComponentScan,@EnableAutoConfiguration,@SpringBootConfiguration 。但是,我们使用@SpringBootApplication批注而不是使用@EnableAutoConfiguration。

@SpringBootApplication = @ComponentScan + @EnableAutoConfiguration + @SpringBootConfiguration

SpringBootApplication注解分析

@SpringBootConfiguration

本质就是@Configuration,Spring在纯注解模式下需要的annotation配置

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

@ComponentScan

复习:Configuration+ComponentScan -> Spring纯注解所需要的最基本annotation方式配置

@ComponentScan


难点:@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 {};
}

可以看到, EnableAutoConfiguration有两个重要的注解:**@AutoConfigurationPackage@Import({AutoConfigurationImportSelector.class})**,分别有两个重要作用。

@AutoConfigurationPackage

​ 作用:把主程序所在包的所有平级类加载并且放入容器,但是不包括子包。整个过程是通过@Import({Registrar.class})实现完成的。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
    String[] basePackages() default {};

    Class<?>[] basePackageClasses() default {};
}

@AutoConfigurationPackage就是通过@Import({Registrar.class})这个注解将Registrar类导入实现把主程序所在包的所有平级类加载并且放入容器。

关键:Registrar类

​ Registrar是AutoConfigurationPackage抽象类中的一个静态内部类。

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    Registrar() {
    }
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
    }
    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.singleton(new AutoConfigurationPackages.PackageImports(metadata));
    }
}

这里registerBeanDefinitions方法的作用就是把主程序所在包的所有平级类加载进容器。里面调用了AutoConfigurationPackages抽象类的register方法,传递了 (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0])这样一个参数,通过metadata获得了获得装配类所在的包,然后调用AutoConfigurationPackages.register方法进行所在包的所有平级类的加载。

这里metadata为:

metadata

也就是我们SpringBootApplication配置类的全类名

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
    // 如果获得的registry的BEAN有BeanDefinition,则直接从BEAN中获取BeanDefinition
    if (registry.containsBeanDefinition(BEAN)) {
        // 配置BasePackagesBeanDefinition
        AutoConfigurationPackages.BasePackagesBeanDefinition beanDefinition = (AutoConfigurationPackages.BasePackagesBeanDefinition)registry.getBeanDefinition(BEAN);
        // 给BasePackagesBeanDefinition加载BasePackages
        beanDefinition.addBasePackages(packageNames);
    } else {// 否则,给registry新建一个BeanDefinition
        registry.registerBeanDefinition(BEAN, new AutoConfigurationPackages.BasePackagesBeanDefinition(packageNames));
    }

}

// 基本包定义类,用ArrayList存储包名
static final class BasePackages {
    private final List<String> packages;
    private boolean loggedBasePackageInfo;

    BasePackages(String... names) {
        List<String> packages = new ArrayList();
        String[] var3 = names;
        int var4 = names.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            String name = var3[var5];
            if (StringUtils.hasText(name)) {
                packages.add(name);
            }
        }

        this.packages = packages;
}

static final class BasePacLinkedHashSetkagesBeanDefinition extends GenericBeanDefinition {
       // 本质就是一个LinkedHashSet
        private final Set<String> basePackages = new LinkedHashSet();
        
        BasePackagesBeanDefinition(String... basePackages) {
            this.setBeanClass(AutoConfigurationPackages.BasePackages.class);
            this.setRole(2);
            this.addBasePackages(basePackages);
        }

        public Supplier<?> getInstanceSupplier() {
            return () -> {
                return new AutoConfigurationPackages.BasePackages(StringUtils.toStringArray(this.basePackages));
            };
        }

        private void addBasePackages(String[] additionalBasePackages) {
            this.basePackages.addAll(Arrays.asList(additionalBasePackages));
        }
}

@import(AutoConfigurationImportSelector.class)

Spring boot应用中使用了注解@SpringBootApplication,该注解隐含地导入了AutoConfigurationImportSelector,如下注解依赖链所示 :

// 注解链
@SpringBootApplication
    => @EnableAutoConfiguration
        => @Import(AutoConfigurationImportSelector.class)

SpringBoot自动配置的核心就在@EnableAutoConfiguration注解上,这个注解通过@Import(AutoConfigurationImportSelector)来完成自动配置。

所以说,SpringBoot自动配置的奥秘就隐藏在AutoConfigurationImportSelector类中。

首先有以下问题:

1.@Import注解是什么?有什么用?

​ @Import注解是用来导入配置类或者一些需要前置加载的类并放入容器,以下是@Imort注解类的源码

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

​ @Target表明了他能作用的范围,可以作用于类、接口、枚举类。

​ 属性只有一个Value,表示的是一个类对象数组,例如value={xx.class,yy.class},表示将xx和yy交给Spring容器管理。(感觉和类加载器有点关系

2.ImportSelector接口是什么?有什么用?

通过ImportSelector 方式导入的类

  1. 新建TestC.class

    public class TestC { public void fun(String str) { System.out.println(str); }
        public void printName() {
            System.out.println("类名 :" + Thread.currentThread().getStackTrace()[1].getClassName());
        }
    }
    

​ 2.新建SelfImportSelector.class 实现ImportSelector 接口,注入TestC.class//TODO ImportSelector 相关解释

public class SelfImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.test.importdemo.TestC"};
    }
}

​ 3.ImportConfig上面引入SelfImportSelector.class

@Import({SelfImportSelector.class})
@Configuration
public class ImportConfig {
}

​ 4.测试结果

@Autowired
TestC testC;

@Test
public void TestC() {
    testC.printName();
}

打印:

ImportAnnotionTest in 7.23 seconds (JVM running for 9.065)
类名 :com.test.importdemo.TestC
2020-08-16 14:23:15.330  INFO 1196 --- [ 

由此得出结论, @Import注解是用来导入配置类需要的类,而实现了ImportSelector接口的类需要Override一个方法,返回一个需要导入配置类的 类名数组,这样,这些导入配置类需要的类 就可以 一次型通过@Import注解被导入到Spring容器中了。

AutoConfigurationImportSelector类

了解一个类,我们首先就要先看这个类的定义:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
      ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

我们可以看到它实现了DeferredImportSelector接口,那么接下来再来看看DeferredImportSelector接口的定义:

public interface DeferredImportSelector extends ImportSelector {

可以看到,DeferredImportSelector接口又继承了ImportSelector接口。这样,我们就明白了,AutoConfigurationImportSelector类必定实现了selectImports()方法,这个方法应该就是SpringBoot能够实现自动配置的核心。

实现接口方法:selectImports

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    // 判断SpringBoot是否开启自动配置
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        // 获取需要被引入的自动配置信息
        AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}

可以看到,selectImports源码没有多少。isEnabled()方法判断SpringBoot是否开启了自动配置。若开启就通过getAutoConfigurationEntry()来获取需要配置的Bean全限定名数组,否则就直接返回空数组。

isEnabled():判断SpringBoot是否开启自动配置

protected boolean isEnabled(AnnotationMetadata metadata) {
   if (getClass() == AutoConfigurationImportSelector.class) {
      // 若调用该方法的类是AutoConfigurationImportSelector,那么就获取EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY的值,默认为true
      return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
   }
   return true;
}

EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY是什么?我们看下它的定义:

String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

看到这里,我们可以猜到这就是在配置文件application.yml或者application.properties中的配置。因此,我们可以在配置文件中来决定SpringBoot是否开启自动配置。

当我们没有配置的时候,默认就是开启自动配置的。

getAutoConfigurationEntry()方法:获取需要自动配置的bean信息

话不多说,先上源码:

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
   // 判断是否开启自动配置
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   // 获取@EnableAutoConfiguration注解的属性
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   // 从spring.factories文件中获取配置类的全限定名数组
   List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
   // 去重
   configurations = removeDuplicates(configurations);
   // 获取注解中exclude或excludeName排除的类集合
   Set<String> exclusions = getExclusions(annotationMetadata, attributes);
   // 检查被排除类是否可以实例化,是否被自动配置所使用,否则抛出异常
   checkExcludedClasses(configurations, exclusions);
   // 去除被排除的类
   configurations.removeAll(exclusions);
   // 使用spring.factories配置文件中配置的过滤器对自动配置类进行过滤
   configurations = getConfigurationClassFilter().filter(configurations);
   // 抛出事件
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

在这里,我们只需要知道这个getAutoConfigurationEntry()方法是用来获取需要自动配置的bean信息,以及里面每个方法做了什么,有个大概的印象就可以了。

下面会对每个方法作更近一步的讲解。

getAttributes():获取@EnableAutoConfiguration注解属性

protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
   String name = getAnnotationClass().getName();
   AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
   Assert.notNull(attributes, () -> "No auto-configuration attributes found. Is " + metadata.getClassName()
         + " annotated with " + ClassUtils.getShortName(name) + "?");
   return attributes;
}

getAttributes调用栈

这里我们可以看到,这个方法就是获取@EnableAutoConfiguration注解的属性。

getCandidateConfigurations():从spring.factories文件获取需要配置的bean

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    //获取需要配置的bean全限定名列表。
    List<String> configurations = 
        new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
    ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you are using a custom packaging, make sure that file is correct.");
    return configurations;
}

getCandidateConfigurations()方法通过SpringFactoriesLoaderloadFactoryNames()方法从所有的spring.factories文件中获取需要配置的bean全限定名列表

getSpringFactoriesLoaderFactoryClass

这里我们看到,AutoConfigurationImportSelector的getSpringFactoriesLoaderFactoryClass 被写死返回本包(org.springframework.boot.autoconfigure)下的EnableAutoConfiguration.class

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}

也就是说,loadFactoryNames()方法获取的也是EnableAutoConfiguration这个注解类下的属性。


然后说说SpringFactoriesLoader这个类

SpringFactoriesLoader

SpringFactoriesLoader会扫描所有jar包类路径下的META-INF/spring.factories文件(写死的),并获取指定接口的配置。

SpringFactoriesLoader

当调用返回值类型为Map<String,List<String>>类型的loadFactories时,内部调用loadSpringFactories,访问本包(org.springframework.boot.autoconfigure)下的META-INF文件夹中的spring.factories。

loadFactories



然后聊聊spring.factories文件,先看看AutoConfigurationImportSelector在什么位置?

AutoConfigurationImportSelector类存在于org.springframework.boot.autoconfigure这个包下,这个包下存储了大量自动化配置类,SpringBoot所有的自动化工作都是在这个包下作用的。

org.springframework.boot.autoconfigure

这些自动化的配置类怎么被加载进容器呢?答案就在META-INF包下的spring.factoriesspring-autoconfigure-metadata.properties。spring.factories文件本质上与properties文件相似,其中包含一组或多组键值对。其中,key的取值是接口的全限定名,value的取值是接口实现类的全限定名。一个接口可以设置多个实现类,不同实现类之间使用,隔开。例如:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration

然而在2.71版本,我并没有在spring.factories文件找到EnableAutoConfiguration,而是找到一些相关的配置,有 过滤掉不符合条件注解配置类的过滤器 的包名 等等,可能是因为版本更新对代码进行了修改,这里不多作文章。

META-INF包下的spring.factories

spring-autoconfigure-metadata.properties

removeDuplicates():去重

protected final <T> List<T> removeDuplicates(List<T> list) {
   return new ArrayList<>(new LinkedHashSet<>(list));
}

我们知道SpringFactoriesLoaderloadFactoryNames()方法会从所有jar包类路径下的META-INF/spring.factories读取配置。就是说会从不同的spring.factories文件中读取配置,那么就有可能会出现配置了相同的类,这里就是对这些数据进行去重。

getExclusions():获取注解的exclude和excludeName属性配置的需要排除的类全限定名集合

protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   Set<String> excluded = new LinkedHashSet<>();
   excluded.addAll(asList(attributes, "exclude"));
   excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
   excluded.addAll(getExcludeAutoConfigurationsProperty());
   return excluded;
}

getExclusions方法就是将上面getAttributes()方法获取到@EnableAutoConfiguration注解的exclude和excludeName属性的值加入到excluded集合中。

checkExcludedClasses():检查排除类

private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
   List<String> invalidExcludes = new ArrayList<>(exclusions.size());
   for (String exclusion : exclusions) {
      // 判断该类是否可以实例化并且自动配置类列表是否包含该类
      if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {
         invalidExcludes.add(exclusion);
      }
   }
   // 无效排除列表若不为空,抛出异常
   if (!invalidExcludes.isEmpty()) {
      handleInvalidExcludes(invalidExcludes);
   }
}

检查被排除的类是否可以实例化以及自动配置类列表是否包含该类。如果存在无效排除类,那么就抛出异常。

getConfigurationClassFilter():获取配置类过滤器

private ConfigurationClassFilter getConfigurationClassFilter() {
   if (this.configurationClassFilter == null) {
      // 获取AutoConfigurationImportFilter过滤器列表
      List<AutoConfigurationImportFilter> filters = getAutoConfigurationImportFilters();
      for (AutoConfigurationImportFilter filter : filters) {
         invokeAwareMethods(filter);
      }
      this.configurationClassFilter = new ConfigurationClassFilter(this.beanClassLoader, filters);
   }
   return this.configurationClassFilter;
}

getConfigurationClassFilter()方法通过getAutoConfigurationImportFilters()方法获取到spring.factories文件中AutoConfigurationImportFilter接口的配置,然后将其封装到ConfigurationClassFilter对象中。

AutoConfigurationImportSelector总结

AutoConfigurationImportSelector类是通过@EnableAutoConfiguration注解引入的,主要作用就是将所有SpringBoot要引入的自动配置类加载并放入容器之中,这些类的名称都是xxxAutoConfiguration,并且他们的全名都是配置在META-INF/spring.factories配置文件中的EnableAutoConfiguration(新版本有所不同,除了这些还有一个强大的功能就是过滤掉不符合条件注解配置类。

AutoConfigurationImportSelector的getSpringFactoriesLoaderFactoryClass 被写死返回本包(org.springframework.boot.autoconfigure)下的EnableAutoConfiguration.class

当调用SpringFactoriesLoader.loadFactoryNames()时,内部调用loadSpringFactories,访问本包(org.springframework.boot.autoconfigure)下的META-INF文件夹中的spring.factories,变量都是所有要加载的xxxAutoConfiguration类。

每一个xxxAutoConfiguration类都有@Configuration 注解,表明这个类是参与Spring配置的。

每一个xxxAutoConfiguration类都有@EnableConfigurationProperties((xxxProperties.class)) annotation,xxxProperties类就是用于存储自动配置信息的类

AutoConfigurationImportSelector类是ImportSelector的实现类,实现了selectImports()方法。selectImports()方法又调用getAutoConfigurationEntry()方法从spring.factories文件中读取配置类的全限定名列表,并进行过滤,最终得到需要自动配置的类全限定名列表。


问题:为什么有了ComponentScan,还需要AutoConfigurationPackage

AutoConfigurationPackage处理的是主程序所在包平级的所有类,ComponentScan(缺省写法,不写value或者basePackages,就是当前主程序所在包)处理的是主程序所在包的平级和子包的所有类。

AutoConfigurationPackage运行在先,ComponentScan运行在后

AutoConfigurationPackage不运行的话,ComponentScan无法运行(AutoConfigurationPackage为ComponentScan做了一系列初始化工作)

SpringBoot自动配置原理总结

这里我们看到,AutoConfigurationImportSelector的getSpringFactoriesLoaderFactoryClass 被写死返回本包(org.springframework.boot.autoconfigure)下的EnableAutoConfiguration.class











体会:初看源码的感觉就像掉进了秦始皇的陵墓一样,令人惊叹的同时又令人充满着困惑,真的是佩服能够书写这种史诗级代码的程序员,这头发掉的值!~

参考资料

SpringBoot自动配置之AutoConfigurationImportSelector - SpringBoot自动配置(二)


文章作者: 银色回廊
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 银色回廊 !
评论
  目录