前面已经手写了starter组件(链接),这次通过源码来分析下自动装配原理
一张图总结
具体分析
SpringBoot特点之一就是约定大于配置,通过扫描约定目录下的特定文件进行解析,解析完成之后通过自动装配类,将Bean加载到容器中。
SpringBoot启动类很简单,加上@SpringBootApplication,运行SpringApplication.run方法即可。
1.@SpringBootApplication如下
图片说明:前四个都是元注解,就不解释了,其余三个
@SpringBootConfiguration :标注当前类是配置类,并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中,并且实例名就是方法名
@ComponentScan::用于自动扫描指定包下的所有组件
@EnableAutoConfiguration:这个注解才是最关键的,先一步步分析下
2.@EnableAutoConfiguration
这个注解最关键的就是导入了AutoConfigurationImportSelector类
@Import:
(1)如果括号中的类实现了ImportSelector接口,spring容器就会实例化此类,并且调用其selectImports方法。
(2)如果实现了DeferredImportSelector接口,spring容器就会实例化Abc类,并且调用其selectImports方法,DeferredImportSelector是ImportSelector的子类,和ImportSelector的实例不同的是,DeferredImportSelector的实例的selectImports方法调用时机晚于ImportSelector的实例,要等到@Configuration注解中相关的业务全部都处理完了才会调用。
(3)如果实现了ImportBeanDefinitionRegistrar接口,spring容器就会实例化此类,并且调用其registerBeanDefinitions方法;
(4)如果没有实现 ImportSelector、DeferredImportSelector、ImportBeanDefinitionRegistrar等其中的任何一个,spring容器就会实例化此类
3.AutoConfigurationImportSelector类
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
//省略其他代码
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
// ...
}
说明:AutoConfigurationImportSelector类DeferredImportSelector接口,会调用selectImports方法,点击方法中getAutoConfigurationEntry方法,重点分析此方法:
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);
}
整体分析:
- getAttributes:获得@EnableAutoConfiguration注解中的属性,即exclude、excludeName
- getCandidateConfigurations:扫描classpath下的META-INF/spring.factories,获得所有的自动装配类,具体请看重点分析
- removeDuplicates:去除重复项
- getExclusions:根据注解中属性exclude、excludeName得到不需要自动装配的配置类并移除
- fireAutoConfigurationImportEvents:广播事件,没有研究。
重点分析
getCandidateConfigurations方法
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
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;
}
点击SpringFactoriesLoader.loadFactoryNames方法。
public final class SpringFactoriesLoader {
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
//...
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
//...
说明
SpringFactoriesLoader 是spring内部提供的一种约定俗成的加载方式。
会扫描classpath下的META-INF/spring.factories文件, spring.factories中的数据是以key=value的形式存储,loadFactoryNames方法通过key获取多个value,也就是配置类。
这也就是手写starter组件时在spring.factories加上配置类可以被springboot启动类执行的原因。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.xiaohou.spring.boot.starter.jdbc.DataSourceAutoConfiguration
总结
- 通过@Import(AutoConfigurationImportSelector.class)实现配置类导入,调用selectImports方法批量加载自动配置类
- 通过SpringFactoriesLoader机制,扫描classpath下的META-INF/spring.factories文件,读取需要装配的配置类
- 通过筛选移除不需要或不符合条件的配置类,完成自动装配。