SpringBootApplication注解解析
@SpringBootApplication 其实是由三个注解组成的
- SpringBootConfiguration
- EnableAutoConfiguration
- ComponentScan
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}), @Filter(type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class})})
public @interface SpringBootApplication {
为了理解@SpringBootApplication这个注解,我们将目标拆分,挨个分析组成@SpringBootApplication的注解
首先来看@SpringBootConfiguration
@SpringBootConfiguration
@Configuration//这里表示SpringBootConfiguration这个类是一个配置类
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
因为@Configuration修饰@SpringBootConfiguration,而@SpringBootConfiguration 修饰 @SpringBootApplication
而@SpringBootApplication 修饰 XxxMainApplication,所以XxxMainApplication其实也相当于被@Configuration修饰了,那么XxxMainApplication其实也就是个配置类==(套娃注解)==
@ComponentScan
注解扫描
@EnableAutoConfiguration
前面两个都意义都不是很大,这个才是硬菜
我们从这个注解的声明来看,可以发现这个注解其实也是一个复合注解,它被
- @AutoConfigurationPackage
- Import修饰着,我们前面讲过@Import可以自动的调用指定类的无参构造器创建对象,并将其放在IOC容器中,这里就是将AutoConfigurationImportSelector这个类的对象放入IOC容器中了
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration
第一条支线:@AutoConfigurationPackage
- 进入
@AutoConfigurationPackage的源码中我们可以看到,这个类通过@Import导入了Registrar.class
@Import({Registrar.class})
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
- 进入Registrar这个类,我们发现这是一个静态内部类
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {}
这个类中有一个重要的方法registerBeanDefinitions
这个方法传入了两个参数:(1)AnnotationMetadata metadata;(2)BeanDefinitionRegistry registry
这个方法的目的是什么呢?其实是:==往容器中批量注册组件==
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
为了弄清楚这个方法中各变量的值,我们在方法体内部打一个断点,如图所示
我们通过idea计算表达式结果小技巧来看看这个表达式获得的值是什么
new AutoConfigurationPackages.PackageImports(metadata).getPackageNames()
计算之,这里获得的是一个包名:com.zztracy.academy.adademyblog,正好是主启动类XxxMainApplication.class所在的包
那么此时我们知道了扫描的包的位置
另外,根据刚才的debug,我们还可以从控制台看到三个变量
- this
- metadata
- registry
其中metadata中存储的是@AutoConfigurationPackage注解的元信息,包括(1)注解标在了哪;(2)它的每一个值是什么,上面我们讲到==(套娃注解)==,那么就相当于这个注解的元信息其实指的也是@SpringBootApplication的元信息
introspected 内省;内观
点开registry,找到beanDefinitionNames,我们可以看到这是一个ArrayList,里面存储的是IOC组件的名字,
其中我们看到的组件名为全类名的,一般为SpringBoot自动导入的??(有待商榷)
而没有全类名的,则是自己编写的,从侧面来说@Component @Controller @Service等都会将对应的类注册进IOC容器中,并且实例的名字为类名
计算出参数之后,再通过这个register方法,批量将组件注入容器中
AutoConfigurationPackages.register(需要扫描的包的位置)
综上所述,这个方法的意义在于
获取注解元信息的包名 -> 转为String数组 -> 通过register这个方法,扫描这个包下面的所有符合条件的组件,注册进IOC容器中
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
第二条支线:@Import({Autoxxx.class})
总的作用:读取springboot启动时需要自动配置的所有类的全类名,但是是否加入到IOC容器中还需要看@Condition相关的注解
@Import({AutoConfigurationImportSelector})
先来看看总的方法调用图,我们可以看到@EnableAutoConfiguration生效之前,一共需要经历6个步骤。其中在最前面的就是@Import({AutoConfigurationImportSelector})中文翻译 自动配置导入选择器,在最后面的是loadSpringFactories()方法,这个也是最终最重要的方法,我们后面会讲到。
自顶向下的看
- 首先ctrl+左键,进入
AutoConfigurationImportSelector这个类, - 直接找到
selectImports()这个核心方法
代码如下:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {//走else逻辑
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
- 进入
getAutoConfigurationEntry()方法
//核心方法 getAutoConfigurationEntry 获取需要自动配置的组件信息
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {//走else逻辑
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//获取组件,并且封装为String集合,重点代码
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//删除重复组件
configurations = this.removeDuplicates(configurations);
//获得需要排除的组件
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
//删除需要排除的组件
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
在这个方法体的第一行打个断点,debug一下
可以看到类型为List的变量configurations看到一共有261个组件(这个依据不同项目的pom文件情况有所不同)
- 接着进入
getCandidateConfigurations
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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方法
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
- 再进入loadSpringFactories方法
最终就是通过这个方法得到一个Map,这个方法的逻辑是从所有maven引入的包下的META-INF/spring.factories这个文件中读取信息
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {//走else逻辑,总的就是遍历所有的maven导入的jar包中META-INF路径下的spring.factories文件中的所有内容
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 factoryTypeName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryImplementationName = var9[var11];
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
我们看一下maven导入的jar包,有些jar包没有spring.factories这个文件,例如:
有些jar包有spring.factories这个文件,其中最核心的是 springboot的autoconfigure包
打开spring.factories我们可以看到许多配置信息,其中我们看到EnableAutoConfiguration
我们可以看到有很多项,并且每一项后面都有换行符,我根据行号数了一下:148 - 22 + 1 = 127
也就是说springboot在这个地方写死一启动就要给容器加载的127个场景下的自动配置类
然而实际上,这些自动配置类所在位置,有很多的@Condition以及其派生注解的修饰,也就是说这些加载的自动配置,最终调用器构造器,注入到IOC容器中的,并不是127个
篇幅太长,只截取了前10个
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
总结:
-
利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
-
利用List configruations = getCandidateConfigurations(annotationMetadata, attributes) 获取到所有需要导入到IOC容器的组件
-
利用工厂加载 ==private static Map<String, List> loadSpringFactories(@Nullable ClassLoader classLoader)==得到所有的组件
-
从META-INF/spring.factories位置加载一个文件
默认扫描当前系统中所有META-INF/spring.factories位置下的文件