一:简述
SpringBoot作为当前最火的java开发框架,它的自动装配帮助我们省略了许多繁琐配置,能够帮助我们快速构建一个项目,那么今天我们就一起分析下SpringBoot实现自动装配的原理。
二:准备工作
Spring的自动装配是基于Spring的SPI机制和@Import注解来实现的。所以我们先简单了解下Spring的SPI机制以及@Import注解的作用。
1:SPI机制
a.什么是SPI
SPI:全称 Service Provider Interface,是一种服务发现机制,它是一种约定大于配置的思想,约定好配置文件路径,配置文件的名称,配置的定义方式等,然后可以通过配置文件的方式来加载需要的类。
注:在jdk,dubbo中也都有定义自己的SPI实现,后续的文章会讲到,感兴趣的同学也可以自己去研究下。
b. Spring中的SPI
spi是一种约定大于配置的思想,所以在使用Spring的SPI时我们需要遵守它的约定。Spring的SPI约定我们要想通过SPI来加载类,需要在ClassPath路径下的META-INF文件夹下定义一个名称为spring.factories的文件,并且它的配置文件的配置方式必须是key-value的形式进行配置。
c. Spring的SPI的使用
1.首先创建一个META-INF文件夹,并且在META-INF下创建一个spring.factories的文件。
2.在配置文件中以key—value的形式书写我们的配置(value如果多个,那么以逗号分割)。
例如:
com.example.Log=com.example.Log4J,com.example.LogBack
3.Spring已经将读取配置文件,解析配置文件的逻辑封装好了,我们只需要利用SpringFactoriesLoader
工具类的api就可以获取到加载的信息。
SpringFactoriesLoader提供了两个方法获取spi的配置.
1.loadFactories()
loadFactories()方法要求读取的配置key是一个父类(或者接口)的全类名,value是这个key的子类的全类名。它可以用来获取配置value的实例。
例如:
spring.factories 配置文件:
com.example.Log=com.example.Log4J,com.example.LogBack
代码:
// Log4J,LogBack必须是Log的子类
List<Log> logs = SpringFactoriesLoader.loadFactories(Log.class, this.getClass().getClassLoader());
//获取到配置文件value的实例
for (Log log : logs){
System.out.println(log);
}
2.loadFactoryNames()
loadFactoryNames()要求key是一个全类名,而value没有限制。loadFactoryNames()可以用来获取配置value的值。
例如:
spring.factories 配置:
com.example.Log=com.example.Log4J,com.example.LogBack
代码:
List<String> classNames = SpringFactoriesLoader.loadFactoryNames(Log.class, this.getClass().getClassLoader());
//获取到value的值 以字符串集合的方式返回
System.out.println(classNames);
2:@Import注解的作用
分为以下几种情况:
a. @Import的value属性为@Configuration的配置类或普通类
作用:将类的实例加入到Spring IoC容器中
b. @Import的value属性是 ImportSelector
接口的实现类
作用:将selectImports()接口返回的类的实例加入到Spring IoC容器中
c. @Import的value属性为DeferredImportSelector接口的实现类
作用:首先会通过DeferredImportSelector的getImportGroup()方法获取的Group,然后调用Group的process()和selectImports(),将selectImports()返回的类的实例加入到Spring IoC容器中。
d. @Import的value属性是ImportBeanDefinitionRegistrar接口的实现类
作用:可以通过实现ImportBeanDefinitionRegistrar的registerBeanDefinitions()方法将需要注入的类的实例加入到Spring IoC容器中。
注:在我的另外一篇文章中有对@Import注解的作用和原理的详细说明 原文地址:@Import注解的使用和原理
三:自动装配原理分析
通过第二节的铺垫,相信大家已经对Spring的SPI和@Import注解都有了一定的了解,那么我们现在对SpringBoot的自动装配原理进行分析。
我们首先看@SpringBootApplication注解,它是一个复合注解,由@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan等注解复合而成,而自动装配是基于@EnableAutoConfiguration注解来实现的,所以我们重点分析@EnableAutoConfiguration注解。
@EnableAutoConfiguration
@EnableAutoConfiguration主要是通过@Import导入AutoConfigurationImportSelector注解实现的自动装配,而AutoConfigurationImportSelector是一个实现了DeferredImportSelector接口的类。
如果@Import注解导入的类实现了DeferredImportSelector接口,那么首先会执行它的getImportGroup()。然后会根据getImportGroup()返回的Group,执行对应Group的process()方法和selectImports()方法(如果getImportGroup()返回null,那么会使用默认的Group:DefaultDeferredImportSelectorGroup
),然后将selectImports()方法返回的类的实例加入到Spring容器中。
public Class<? extends Group> getImportGroup() {
return AutoConfigurationGroup.class;
}
getImportGroup()方法返回AutoConfigurationGroup,所以接下来会执行AutoConfigurationGroup的process()方法和selectImports()收集需要导入的类,并且将selectImports()返回的类加入到Spring容器中。所以我们主要分析AutoConfigurationGroup的process()方法和selectImports()方法。
首先我们看process()方法做了什么
process()
process()调用getAutoConfigurationEntry方法获取到需要自动装配的类,并将它们保存在 autoConfigurationEntries中。
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
//通过getAutoConfigurationEntry()方法获取需要自动转配的类
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(annotationMetadata); this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
getAutoConfigurationEntry()
getAutoConfigurationEntry()通过getCandidateConfigurations()方法获取自动装配的类,然后会进行去重,过滤不需要导入的类(包括application.properties文件中的spring.autoconfigure.exclude配置的类,@SpringBootApplication注解配置的exclude和excludeName的类,AutoConfigurationImportFilter的match()方法返回true的类),然后将处理完的类封装成AutoConfigurationEntry。
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);
//获取spring.factories中AutoConfigurationImportFilter的实现类并且循环调用它们的match()方法
configurations = getConfigurationClassFilter().filter(configurations);
//发布自动装配的事件
fireAutoConfigurationImportEvents(configurations, exclusions);
//封装成AutoConfigurationEntry返回
return new AutoConfigurationEntry(configurations, exclusions);
}
getCandidateConfigurations()
getCandidateConfigurations()方法通过SPI的方式获取key为EnableAutoConfiguration全类名的value。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//通过spi机制获取需要自动装配的类
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
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;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
selectImports()
public Iterable<Entry> selectImports() {
if (this.autoConfigurationEntries.isEmpty()) {
return Collections.emptyList();
}
//获取所有的需要排除的类
Set<String> allExclusions = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
//获取需要导入的类
Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
.collect(Collectors.toCollection(LinkedHashSet::new));
processedConfigurations.removeAll(allExclusions);
//根据AutoConfigureOrder进行排序 然后将导入的类封装成Entry返回。
return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
.map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
.collect(Collectors.toList());
}
Spring在处理@Import注解的时候会将selectImports()方法返回的类的实例加入到Spring容器中,也就完成了自动装配。
四:总结
自动装配主要是根据@Import注解和SPI机制来完成的,所以要理解自动装配首先需要了解@Import注解和SPI机制。大致的流程就是通过SPI机制获取到spring.factories中key为EnableAutoConfiguration的value(需要自动装配的全类名),然后将获取到的类名通过@Import注解将对应的实例加入到Spring容器中。