浅析SpringBoot-自动装配的实现

基于Springboot 2.2.4.RELEASE、jdk8

前言

​ 最近在进行spring cloud alibaba的学习,由于cloud是建立在boot之上的,所以决定先把一知半解的boot原理弄懂,其中最重要的便是“自动装配”这个概念。boot这个巧妙的设计,让java程序员脱离了各种xml配置的苦海,那它是如何在程序启动的时候,自动装载我们需要的bean到容器里面的呢?源码里有一切我们想要的答案~~~

先导知识

​ 要想了解boot自动装配的实现,得先了解下几个注解。方便后面阅读源码。

1. @ConfigurationProperties

​ 标有此注解的类的所有属性,会自动和配置文件中的配置项进行绑定(默认从全局配置文件中获取配置值)。

​ 如配置@ConfigurationProperties(prefix="spring.redis"),则表示此类中的属性默认从配置文件中的spring.redis下对应配置的属性值获取。

2.@Import

将符合条件的的bean,注入到IoC容器中。详细可参考https://mp.weixin.qq.com/s/dNOBwMPHKdccmeJFWzzTOg

在spring4.2之前只支持导入配置类。在4.2之后支持导入普通的java类,并将其声明成一个bean。

​ Import的3种使用方式分别是:

  1. 直接导入普通java类

    @Import({User.class})

  2. 配合自定义的ImportSelector使用,只有当条件满足时才注入bean

    @Import({MyUserSelector.class})

  3. 配合ImportBeanDefinitionRegistrar使用

    在第一点中,直接把类注入到IOC容器中,只能调用类的无参构造函数,如果想对类进行个性化定制,就可以用此方式,手动将bean注册到容器中(需配合@Configuration使用)。

3.@Conditional

该注解可以实现满足一定条件时,才启用配置。

常用的一些扩展注解有:

ConditionalOnBean :容器中存在指定 Bean,则生效。

ConditionalOnMissingBean:容器中不存在指定 Bean,则生效。

ConditionalOnClass: 系统中有指定的类,则生效。

ConditionalOnMissingClass: 系统中没有指定的类,则生效。

ConditionalOnProperty: 系统中指定的属性是否有指定的值。

ConditionalOnWebApplication: 当前是web环境,则生效。

自动装配的实现

​ 自动装配在springboot中是通过@EnableAutoConfiguration注解来开启的,在启动类的注解@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 {
  //...
}

​ 这里对@Enable注解进行一下说明,在spring3.1版本就已经开始支持@Enable注解了,主要作用就是把相关组件的Bean装配到IoC容器中。在此之前,使用基于JavaConfig的形式来完成Bean的装载,则必须使用@Configuration及@Bean。而@Enable本质上是针对这两个注解的封装,所以不难发现@Enable系列的注解都会携带一个@Import注解,Spring在解析到@Import注解的时候,会根据Import导入的配置类来实现Bean的装配。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
  //...
}

​ AutoConfigurationImportSelector类实现了ImportSelector接口,它只有一个selectImports的抽象方法,返回一个string数组,这个数据返回的类最终会被装配到IoC容器中。和@Configruation不同的是,ImportSelector可实现批量装配,并且还可以通过逻辑处理来选择要装配的类,也就是说可以根据上下文来决定哪些类能被IoC容器初始化。

public interface ImportSelector {

   String[] selectImports(AnnotationMetadata importingClassMetadata);

   @Nullable
   default Predicate getExclusionFilter() {
      return null;
   }

}

​ 接下来看一下AutoConfigurationImportSelector.class的具体实现。先进入selectImports方法,此方法也是ImportSelector的核心方法,主要用于批量获取需要注入的配置类。

//主要看这个方法
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return NO_IMPORTS;
   }
  //这里获取自动配置的信息条目,返回最终需要的配置类信息
   AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
   return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

​ getAutoConfigurationEntry是其中主要的方法,用于获取实际可以注入的配置类。

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
  //获取候选的配置类信息
   List configurations = getCandidateConfigurations(annotationMetadata, attributes);
  //进行一系列过滤判断,得到最终要返回的配置信息
   configurations = removeDuplicates(configurations);
   Set exclusions = getExclusions(annotationMetadata, attributes);
   checkExcludedClasses(configurations, exclusions);
   configurations.removeAll(exclusions);
   configurations = getConfigurationClassFilter().filter(configurations);
   fireAutoConfigurationImportEvents(configurations, exclusions);
   return new AutoConfigurationEntry(configurations, exclusions);
}

​ getCandidateConfigurations会调用SpringFactoriesLoader.loadFactoryNames去META-INF/spring.factories配置文件下获取key为EnableAutoConfiguration的配置信息回来。

protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//getSpringFactoriesLoaderFactoryClass()获取回来的正是EnableAutoConfiguration,在loadFactoryNames会从META-INF/spring.factories配置中加载回来的map中,找到key为EnableAutoConfiguration的配置类作为候选返回list。
  List configurations = 
 SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
         getBeanClassLoader());
  //默认从META-INF/spring.factories拿配置,体现了springboot的核心思想“约定优于配置”。
   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;
}

SpringFactoriesLoader.loadFactoryNames就是实际去配置文件META-INF/spring.factories获取所有配置类信息回来的方法。

public static List loadFactoryNames(Class factoryType, @Nullable ClassLoader classLoader) {
   String factoryTypeName = factoryType.getName();
  //getOrDefault(factoryTypeName, Collections.emptyList())获取的就是key为EnableAutoConfiguration的map
   return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) {
  //...
   try {
     //这里就是实际获取META-INF/spring.factories路径的地方
      Enumeration urls = (classLoader != null ?
            classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
            ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
      //....
      return result;
   }
  //...
}

​ 以下是源码走读的顺序图

springboot自动装配代码顺序.jpg

参考博文

https://juejin.im/post/5ce5effb6fb9a07f0b039a14#heading-11

你可能感兴趣的:(浅析SpringBoot-自动装配的实现)