一、什么是Spring Boot的自动配置
Spring Boot的最大的特点就是简化了各种xml配置内容,之前使用SSM框架时我们配置的许多内容,例如数据源、连接池、会话工厂、事务管理···,而现在这些都由Spring Boot来自动配置。简单来说就是用注解来对一些常规的配置做默认配置,简化xml配置内容,使项目能够快速运行。
二、Spring Boot自动配置原理
想了解原理,就需要从头开始追根溯源
首先我们打开Spring Boot启动类
启动类中唯一的注解@SpringBootApplication自然成了我们关注的重点,它是Spring Boot的核心注解,也是一个组合注解,ctrl+鼠标左键点进去
我们进入这个注解可以看到里面又包含了其它很多注解,但其中@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个注解尤为重要,这三个注解的作用分别是:
@SpringBootConfiguration:SpringBoot的配置类 ,标注在某个类上 , 表示这是一个SpringBoot的配置类,继承自Configuration,支持JavaConfig的方式进行配置。
@EnableAutoConfiguration:开启自动配置,是实现自动配置最主要的注解。
@ComponentScan:自动扫描组件,默认扫描该类所在包及其子包下所有带有指定注解的类,将它们自动装配到bean容器中,会被自动装配的注解包括@Controller、@Service、@Component、@Repository等。也可以指定扫描路径。
继续进入@EnableAutoConfiguration注解
又发现两个特殊的注解:
@AutoConfigurationPackage : 自动配置包,将主配置类(@SpringBootApplication标注的类)的所在包及下面所有的子包里面的所有组件扫描到Spring容器中。所以controller、service之类的包必须在和主启动类在同一包下。
@Import(AutoConfigurationImportSelector.class),它导入了AutoConfigurationImportSelector类。这个类中有一个非常重要的方法——selectImports(),它几乎涵盖了组件自动装配的所有处理逻辑,包括获得候选配置类、配置类去重、排除不需要的配置类、过滤等,最终返回符合条件的自动配置类的全限定名数组。
下面源码图中对该方法进行了详尽的注释
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
//检查自动配置功能是否开启,默认开启
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
//加载自动配置的元信息
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//获取候选配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
//去掉重复的配置类
configurations = removeDuplicates(configurations);
//获得注解中被exclude和excludeName排除的类的集合
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
//检查被排除类是否可实例化、是否被自动注册配置所使用,不符合条件则抛出异常
checkExcludedClasses(configurations, exclusions);
//从候选配置类中去除掉被排除的类
configurations.removeAll(exclusions);
//过滤
configurations = filter(configurations, autoConfigurationMetadata);
//将配置类和排除类通过事件传入到监听器中
fireAutoConfigurationImportEvents(configurations, exclusions);
//最终返回符合条件的自动配置类的全限定名数组
return StringUtils.toStringArray(configurations);
}
需要注意如何得到候选的配置类,可以看到所有的配置信息通过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;
}
点进去loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());这个方法(类加载器)
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryClassName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryName = var9[var11];
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
通过以上代码我们发现:getCandidateConfigurations()方法通过SpringFactoriesLoader.loadFactoryNames()类加载器加载META-INF/spring.factories文件,通过这个文件获取到每个配置类的url,再通过这些url将它们封装成Properties对象,最后解析内容存于Map<String,List<String>>中。
spring-boot-autoconfigure-x.x.x.x.jar里就有一个这样的spring.factories文件,这个文件也是一组一组的key=value的形式,其中一个key是EnableAutoConfiguration类的全类名,而它的value是一个xxxxAutoConfiguration的类名的列表,这些类名以逗号分隔,根据文件的内容格式我们可以清晰地了解Map<String,List<String>>中都存了些什么。
最后通过loadFactoryNames传递过来的class名称作为Key从Map中获得该类的配置列表,而这个class名称是什么呢?回到之前的SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader())方法,注意第一个参数,是一个方法,我们进入这个方法,发现返回的是EnableAutoConfiguration.class,这即是我们需要的class名称。
最终,getCandidateConfigurations()方法获取到了候选配置类并存于List中。之所以说是“候选”,是因为它们后续还需要经过一系列的去重、排除、过滤等操作,通过selectImports()方法返回一个自动配置类的全限定名数组,然后将所有自动配置类加载到Spring容器中,这样就实现了自动装配。