Spring boot的自动装配原理

1,085 阅读4分钟

什么是自动装配?

自动装配,它是Starter的基础,也是Spring Boot的核心; 简单来说,就是自动将Bean装配到Ioc容器中。

常见的Starter如下:
web服务Starter:

<dependency>
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

引入mybatis框架:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>

引入redis:

<dependency>
   <groupId>org.springframework.boot</groupId>\
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

自动装配的实现

Spring Boot项目启动类中有个注解@SpringBootApplication,在这个注解中可以看到另外一个注解 @EnableAutoConfiguration,自动装配就是通过这个注解来开启的。

@SpringBootApplication
public class DemoApplication {
   public static void main(String[] args) {
      SpringApplication.run(DemoApplication.class, args);
   }
}
@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 {
    // ...
}

进入到@EnableAutoConfiguration注解里面,可以发现又有注解@AutoConfigurationPackage@Import,其中@AutoConfigurationPackage注解的作用是把使用了该注解的类所在的包及子包下所有组件扫描到Spring Ioc容器中,@Import注解中导入的并不是一个Configuration的配置类,而是一个oConfigurationImportSelector这一点看,它就和其他@Enble注解有很大的不同。

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

不管AutoConfigurationImportSelector类是什么,它跟注解@Configuration的·作用一样,都是实现配置类的导入,但是他们之间存在区别,下面看下这个类的源码

AutoConfigurationImportSelector

AutoConfigurationImportSelector类实现DeferredImportSelector,他只有一个selectImports抽象方法,并继承自ImportSelector,并且返回一个String数组,在这个数组中可以指定需要装配到IOC容器的类,当在@Import中导入了一个ImportSelector的实现类之后,会把该实现类中返回的Class名称都装载到Ioc容器中。
@Configuration不同的是,ImportSelector可以实现批量装配,并且还可以通过逻辑处理来实现Bean的选择性配置,也就是可以根据上下文来决定哪些类能够Ioc容器初始化。

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

public interface DeferredImportSelector extends ImportSelector {
    // ...
}

public interface ImportSelector {

    String[] selectImports(AnnotationMetadata importingClassMetadata);

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

模拟ImportSelector的使用

  • 创建两个类,需要将这两个类装载到Ioc容器中
public class FirstClass {
}
public class SecondClass{
}
  • 创建一个ImportSelector的实现类,并实现将这两个Bean装配到Ioc容器中
public class WhiteImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{FirstClass.class.getName(),SecondClass.class.getName()};
    }
}
  • 自定义注解@Enable
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({WhiteImportSelector.class})
public @interface WhiteEnable {
}
  • 在启动类添加注解@WhiteEnable,即可通过getBean()获取Ioc容器中的对象实例:
@SpringBootApplication
@WhiteEnable
public class DemoApplication {
   public static void main(String[] args) {
      ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class,args);
      FirstClass firstClass = context.getBean(FirstClass.class);
      System.out.println(firstClass);
   }
}

启动完毕输出,说明装配成功:
com.white.demo.starter.FirstClass@1932d482

自动装配原理分析

通过上面的分析可以发现```selectImports``方法是自动装配的核心,它的主要作用是:

  • AutoConfigurationMetaDataLoader.loadMetadata从META-INF/spring-autoconfigure-metadata.properties中加载自动装配的条件元数据,简单来说就是只有满足条件的Bean才能够进行装配

  • 收集所有符合条件的配置类autoConfigurationEntry.getConfigurations(),完成自动装配

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}

最终都会调用getAutoConfigurationEntry()方法获得所有需要自动装配的配置类。

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
        // 获取@EnableAutoConfiguration注解中属性exclude、excludeName等中的值
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
       // 获得所有自动装配的配置类
        List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
        // 去除重复的配置项
        configurations = this.removeDuplicates(configurations);
        // 将不需要自动装配的配置类移除
        Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
        this.checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = this.getConfigurationClassFilter().filter(configurations);
        // 广播事件
        this.fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
    }
}

核心方法getCandidateConfigurations

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // Spring中约定俗成的加载方式,类似于Java中的SPI机制
    // 这里会扫描classpath下的META-INF/spring.factories文件
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}

spring.factories文件中的数据以Key=Value形式存储,而SpringFactoriesLoader.loadFactoryNames会根据Key得到对应得value的值,因此,在这个场景下,Key对应为EnableAutoConfiguration,Value是多个配置类,也就是 getCandidateConfigurations方法所返回的值:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration

通过查看SpringApplicationAdminJmxAutoConfiguration类,实际上就是一个添加了@Configuration的配置类

@Configuration(
    proxyBeanMethods = false
)
@AutoConfigureAfter({JmxAutoConfiguration.class})
@ConditionalOnProperty(
    prefix = "spring.application.admin",
    value = {"enabled"},
    havingValue = "true",
    matchIfMissing = false
)
public class SpringApplicationAdminJmxAutoConfiguration {
    // ...
}

至此,自动装配的原理基本上就分析完了,核心流程如下:

  • 通过@Import(AutoConfigurationImportSelector)实现配置类的导入,但是这里并不是传统意义上的单个配置类装配,与注解@Configuration的区别

  • AutoConfigurationImportSelector类实现了ImportSelector接口,重写了方法selectImports,它用于实现选择性批量配置类的装配

  • 通过Spring提供的SpringFactoriesLoader机制,扫描classpath路径下的META-INF/spring.factories,读取需要实现自动装配的配置类

  • 通过条件筛选的方式,把不符合条件的配置类移除,最终完成自动装配

手写starter

Starter的命名规范

  • 官方命名的格式: spring-boot-starter-模块名 如: spring-boot-starter-web

  • 自定义命名的格式: 模块名-spring-boot-starter 如:base-spring-boot-starter