在梳理自动配置原理之前我们要怀着这样两个疑问,才能更好的理解自动配置流程原理
- 为什么我们不用配置包扫描,但是我们的组件依旧可以被注册?
- 为什么我们一启动springboot项目,它会帮我们自动生成那么多组件?
1、SpringBoot主程序启动类
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
被@SpringBootApplication
修饰的就是springboot主程序启动类,每次我们直接直接启动这个启动类,SpringBoot就启动成功了,并且帮我们配置了好多自动配置类。
一切原因都是因为 @SpringBootApplication
这个注解,我们来查看一下该注解。
2、@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 {
这里只进行解释自动配置的流程原理,不看元注解,其中除了元注解,这三个注解@SpringBootConfiguration @EnableAutoConfiguration @ComponentScan
就显得很重要了,当然这三个注解中也是有轻有重的,先简单说明三个注解的作用,然后再详细说明
@SpringBootConfiguration
:表示被改注解修饰的类是一个配置类,当然,因为注解是具有传递性的,也就意味着,被@SpringBootApplication
注解修饰的类是一个配置类@EnableAutoConfiguration
:开启自动配置的注解,这正是自动配置的精华所在@ComponentScan
:包扫描注解,源码之中,这是一个@ComponentScan
注解的高级使用方式,这里并不影响我们理解自动配置的流程,所以不做解释。
由此我们可以看到如果我们需要理解自动配置原理,那么这其中最重要的注解就是@EnableAutoConfiguration
了
3、@EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
同样的,我们不需要关注元注解,我们只需要看@AutoConfigurationPackage和@Import
注解。
同样的,我们先简单说明这两个注解的作用,然后再详细解释
@AutoConfigurationPackage
:自动配置包注解,用于给包结构下的所有组件进行注册,详细查看这个注解就能明白,为什么我们不需要配置包扫描就可以注册组件了@Import
:导入自动配置组件
4、@AutoConfigurationPackage
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
可以看到,其实这个注解就是导入了一个这个类型的AutoConfigurationPackages.Registrar.class
组件,我们来看这个组件的作用是什么
AutoConfigurationPackages.Registrar.class
组件
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
其实他就是批量注册了,主配置文件同级以及子集的组件,这个@Import(AutoConfigurationPackages.Registrar.class)
就是@import
注解的一个高级用法,作用就是批量注册。
根据debug查看可以看到其实new PackageImports(metadata).getPackageNames()
就是获取的当前被@SpringBootApplication
注解修饰的类所在的包,然后它会根据这个包按照,前面说的,“注册同级及其自己的文件的组件”的规则进行注册。(根据上图可以看到:bean包、config包、controller包中的注解都可以被扫描到)
所以这时候我们就清晰的明白我们前面最开始提出的第一个疑问了:为什么我们不用自己配置包扫描还能自动被注册,正是因为这个@Import(AutoConfigurationPackages.Registrar.class)
注解
5、@Import(AutoConfigurationImportSelector.class
这个注解同样是@Import
注解的高级用法:批量注册。
那么它是如何个批量注册法呢,我们查看这个AutoConfigurationImportSelector
类
AutoConfigurationImportSelector.class
组件
根据@import
的高级用法——批量注册,这个类实现的最高父接口使用idea的关系树查看,可以看到,这个是使用实现ImportSelector
接口的批量注册的方式而ImportSelector
这接口中的String[] selectImports(AnnotationMetadata importingClassMetadata);
方法就是用于批量注册的方法,这个方法返回的数组就是我们要批量注册的组件的全类名,然后我们在AutoConfigurationImportSelector
类中找到这个方法,如下
selectImports(AnnotationMetadata annotationMetadata)
方法
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
简单看一下返回的值是根据autoConfigurationEntry.getConfigurations()
进行转为字符串数组的,所以我们需要看一下autoConfigurationEntry 这个类使用getConfigurations()获取的Configurations是什么,记住我们要看的是什么,不然会因为看不懂源码而迷失自己。
所以我们需要查看getAutoConfigurationEntry(annotationMetadata)
这个方法
getAutoConfigurationEntry(annotationMetadata)
方法
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);
}
我们可以看到,getAutoConfigurationEntry(annotationMetadata)
方法中最终返回的new AutoConfigurationEntry(configurations, exclusions)
中就传入了configurations,而这个configurations,就是我们前面获取的configurations
看这个行代码List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
见名知意,获取的就是候选配置,而候选配置都有什么,我们需要进一步查看
同样需要明确我们进入getCandidateConfigurations(annotationMetadata, attributes)
这个方法要查看的是什么:getCandidateConfigurations(annotationMetadata, attributes)获取到的候选配置是什么?
getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)
方法
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;
}
我们看到返回的List<String> configurations
是根据SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass()
这个方法获取的,我们继续深入查看这个方法
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass()
方法
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
依旧是明确我们是来干什么的:查看SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());获取的List<String>
到底是什么配置
所以我们需要在进入到loadSpringFactories(classLoader)
查看
loadSpringFactories(classLoader)
方法
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);
}
}
这段源码太长了,容易使大家产生误会,所以不过多解释。只言简意赅的解释这个方法中的作用这个方法就是用于加载所有位于META-INF/spring.factories
目录下的文件,debug查看可知其实这个目录下的文件有很多,但是最主要的是spring自动配置jar中的这个文件中,如下图它会去将# Auto Config
中的内容加载为一个List<String>
数组,
所以回答上一个问题就是:SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
获取的List<String>
到底是我们位于META-INF/spring.factories
目录下的spring.factories文件中需要自动配置的全限定类名
然后我们回到最开始的需要看一下autoConfigurationEntry 这个类使用getConfigurations()获取的Configurations是什么这个问题,这个类就是用于加载,自动配置jar的META-INF/spring.factories
目录下的spring.factories文件中的全限定类名的
debug验证
然后最最最重要的一步就是springboot 的精髓按需生效,实现按需生效的注解就是@Conditional
这一系列注解
,如下图这里不做一一解释了,这个注解的作用就是满足条件才生效
总结
由此springboot的自动配置就再次分析了一边,这一边显然比第一遍要更加清晰了,总结如下
- SpringBoot启动的时候加载主配置类,开启了自动配置功能@EnableAutoConfiguration
- @EnableAutoConfiguration的作用是利用AutoConfigurationImportSelector给容器中导入一些组件。
- 可以查看public String[] selectImports(AnnotationMetadata annotationMetadata)方法的内容。
- 通过protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)获取候选的配置,这个是扫描所有jar包类路径下"META-INF/spring.factories";
- 然后把扫描到的这些文件包装成Properties对象。
- 从properties中获取到EnableAutoConfiguration.class类名对应的值,然后把他们添加在容器中。
- 整个过程就是将类路径下"META-INF/spring.factories"里面配置的所有EnableAutoConfiguration的值加入到容器中。
- 每一个这样XXAutoConfiguration类都是容器中的一个组件都加入到容器中,用他们来做自动配置。每一个自动配置类进行自动配置功能,以HttpEncodingAutoConfiguration为例解释自动配置原理。
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ServerProperties.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
- 根据当前不同的条件判断,决定这个配置是否生效。
参考文档如下