youyichannel

志于道,据于德,依于仁,游于艺!

0%

康康什么是SpringBoot的自动装配

这个问题可以从三个问题来回答:

  1. 什么是SpringBoot自动装配?
  2. SpringBoot是如何实现自动装配的?如何实现按需加载的?
  3. 如何实现一个Starter?

前言

没有SpringBoot的时候,也就是使用原生Spring时,我们需要使用XML配置,比较繁琐。即使Spring后面引入了基于注解的配置,我们在开启某些Spring特性或者引入第三方依赖的时候,还是需要使用XML或者Java进行显示配置。

在有了SpringBoot之后呢,我们只需要添加相关的依赖,无需配置,添加@SpringBootApplication注解即可。并且还可以通过修改SpringBoot的全局配置文件application.properties或者application.yml即可对项目进行自定义配置,比如修改启动端口号、配置数据库信息等。

那么为什么SpringBoot能这么干呢?

这就得益于自动装配机制了,可以说,自动装配是SpringBoot的核心

什么是SpringBoot的自动装配?

Spring Boot 自动装配是基于 Java 的 SPI(Service Provider Interface) 和 Spring Framework 的条件注解 Conditional On Class 等功能实现的。

SpringBoot定义了一套接口规范,即SpringBoot在启动是会扫描外部引用Jar包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到Spring容器,并执行类中定义的各种操作。

对于外部Jar来说,只需要按照SpringBoot定义的标准,就能将自己的功能装配进SpringBoot。

在创建 Spring Boot 项目时,我们会引入很多 Starter,比如 Web, Data JPA, Security 等,它们都包含了一些已经为我们写好并默认配置好的组件,如 Tomcat, Hibernate, Spring Security 等等。当我们引入这些 starter 之后,Spring Boot 就能根据 starter 中 META-INF/spring.factories 配置的值,但是不一定会全部生效,需要判断条件是否成立(比如:RabbitAutoConfiguration类上有注解@ConditionalOnClass({ RabbitTemplate.class, Channel.class }),意味着要导入括号内的两个类才会装配RabbitAutoConfiguration),只要导入了对应的Starter,就会有对应启动器,有了启动器,就可以自动装载对应的 Bean 到容器中。

SpringBoot是如何实现自动装配的?

先来看SpringBoot的核心注解@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 {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};

@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};

// ...
}

可以将@SpringBootApplication注解看作是@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan注解的组合,这三个注解的作用是:

  • @SpringBootConfiguration:该注解实际上等价于@Configuration,允许在上下文中注册额外的Bean或者导入其他配置类
  • @EnableAutoConfiguration:开启SpringBoot的自动配置机制
  • @ComponentScan:扫描被@Component注解的Bean,该注解默认会扫描启动类所在包下的所有的类,可以通过属性basePackages指定扫描包,也可以自定义不扫描某些Bean

很显然,其中的@EnableAutoConfiguration注解是实现自动装配的重要注解,弄他!

@EnableAutoConfiguration:实现自动装配的核心注解

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

/**
* Environment property that can be used to override when auto-configuration is
* enabled.
*/
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};

/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};

}
  • @AutoConfigurationPackage:作用使用main包下的所有组件注册进容器中
  • @Import:加载自动装配类,形如 xxxAutoConfiguration

那么就很显然了,该注解引入的AutoConfigurationImportSelector类是个核心类

AutoConfigurationImportSelector:加载自动装配类

继承体系:

public interface ImportSelector {

/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
* @return the class names, or an empty array if none
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);

// ...
}

AutoConfigurationImportSelector类实现了ImportSelector接口,也就实现了该接口中的selectImports()方法,该方法主要用于获取所有符合条件的类的全限定名,这些类需要被加载IOC容器中。

AutoConfigurationImportSelector#selectImports()

private static final String[] NO_IMPORTS = {};
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 判断是否开启了自动装配
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 获取所有需要装载的Bean
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

此处呢,需要重点关注getAutoConfigurationEntry()方法,这个方法主要负责加载自动装配类。

方法追踪链:

  1. org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry
  2. org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getCandidateConfigurations
  3. org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames
  4. org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories
/**
* 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);
// 第三步
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);
}

第一步:判断自动装配开关是否开启,默认为spring.boot.enableautoconfiguration=true,可以在application.properties中配置

第二步:获取@EnableAutoConfiguration注解中配置的excludeexcludeName属性

第三步:获取需要自动装配的所有配置类,读取META-INF/spring.factories文件,所有 Spring Boot Starter 下的META-INF/spring.factories都会被读取到

第四步:筛选配置,@ConditionalOnXXX 中的所有条件都满足,该类才会生效,才会装配进IOC容器

比如:

@Configuration
// 检查相关的类:RabbitTemplate 和 Channel是否存在,存在才会加载
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class)
public class RabbitAutoConfiguration {
}

SpringBoot提供的条件注解:

  • @ConditionalOnBean:当容器里有指定 Bean 的条件下
  • @ConditionalOnMissingBean:当容器里没有指定 Bean 的情况下
  • @ConditionalOnSingleCandidate:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean
  • @ConditionalOnClass:当类路径下有指定类的条件下
  • @ConditionalOnMissingClass:当类路径下没有指定类的条件下
  • @ConditionalOnProperty:指定的属性是否有指定的值
  • @ConditionalOnResource:类路径是否有指定的值
  • @ConditionalOnNotWebApplication:当前项目不是 Web 项目的条件下
  • @ConditionalOnWebApplication:当前项目是 Web 项目的条件下

如何开发一个Starter

为什么需要Starter?

理想情况:开发者只需要关心调用那些接口、传递哪些参数,就跟调用自己写的代码一样简单。

开发Starter的好处:开发者引入Starter之后,可以直接在application.yml中写配置,自动创建客户端。

进一步说明

为了方便开发者的调用,我们不能让他们每次都自己编写签名算法,这显然很繁琐。因此,我们需要开发一个简单易用的 SDK,使开发者只需关注调用哪些接口、传递哪些参数,就像调用自己编写的代码一样简单。实际上,RPC(远程过程调用)就是为了实现这一目的而设计的。RPC它就是追求简单化调用的理想情况。类似的例子是小程序开发或者调用第三方 API,如腾讯云的 API,它们都提供了相应的 SDK。

如何开发Starter?

  • 确认所需要的依赖,需要引入spring-boot-configuration-processor(配置文件编写提示)
  • 过程代码编写
  • 配置类编写
  • resources目录下创建META-INF/spring.factories,其中配置自动配置类org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxx

最后就是打包安装发布,供其他项目使用。

总结

SpringBoot通过@EnableAutoConfiguration注解开启自动装配,通过SpringFactoriesLoader加载META-INF/spring.factories中的自动配置类实现自动装配,自动装配类其实就是通过@Conditional按需加载配置类,想要期望的类被装配就需要引入相关的依赖包。