spring boot开箱即用的特点,靠的就是强大的自动装载功能,spring boot会扫描classpath下所有依赖的spring.factories文件,将EnableAutoConfiguration配置下的所有类加载到spring容器中。
@Import注解和@Bean一样,都是将对象添加到容器中,但是不同的是@Bean是加在方法上,要自己在方法中new对象,且都和@Configuration注解配合使用。
而@Import更加灵活,可以直接导入普通类,和实现ImportSelector或ImportBeanDefinitionRegistrar接口的类搭配使用,通过代码更灵活的往spring容器中注入对象。也可以搭配@Configuration注解使用。
ImportSelector接口的主要方法是String[] selectImports(AnnotationMetadata importingClassMetadata)。是一种灵活的注册对象到spring 容器中的方法。怎么灵活呢?通过AnnotationMetadata这个类,可以获取到指定注解上字段的值,例如spring cloud中常用的@EnableXXX这类注解,搭配ImportSelector接口,可以控制组件关键对象的注册,以达到控制组件是否开启的目的。
ImportBeanDefinitionRegistrar接口的方法为registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry),通过接口上的BeanDefinitionRegistry向spring容器添加对象,通过Import引入时,spring会自动调用registerBeanDefinitions方法。
和ImportSelector的区别?ImportSelector还是返回类名让spring去加载配置类,而ImportBeanDefinitionRegistrar是直接向spring容器中添加对象,在已知需要哪些Bean的时候,用ImportSelector更加方便。如果逻辑相对复杂,需要代码去判断实现,比如多数据源的情况下,你不知道有几个数据源,需要代码判断动态添加,则只能用ImportBeanDefinitionRegistrar。
上面讲了那么多spring boot常用的自动装载的方法,又是META-INF/spring.factories文件,又是@Import注解,那么spring boot是如何实现这些东西的呢,今天来探究一下spring boot的源码,这里我用的是spring boot 2.7.2版本,各个版本略有不同,接下来一起探究一下。
spring boot的入口十分简单,大家应该都知道,就是一个静态的run方法,让我们从这个run方法开始debug,org.springframework.boot.SpringApplication#run(java.lang.String…)。
通过方法名我们就可以看出来,这里进行了一些简单的处理,包括banner的打印,环境变量的准备等等,注意蓝色这行的方法,F5进去追踪一下。
refreshContext方法包含了一个shutdownHook的注册,这个没什么好看的,直接进入refresh方法,下面还有一个对refresh方法的调用,跳过,直接进入refresh方法的主体org.springframework.context.support.AbstractApplicationContext#refresh。
打标的前面几行,都是一些类的创建,字段的初始化,到了this.invokeBeanFactoryPostProcessors(beanFactory);里面做了大量的处理,我们进入这个方法探究一下做了哪些处理。
还没进入到方法的主体,继续F5进入方法,找到执行的主体,org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, java.util.List
前面进行了一些加载器的处理,直接进入到蓝色的这个方法org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors
继续debug进箭头指的这个方法,org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
看箭头所指的这个方法,看方法名,应该是加载配置类的,debug进这个方法org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
...............
if (!configCandidates.isEmpty()) {
..................
ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set candidates = new LinkedHashSet(configCandidates);
HashSet alreadyParsed = new HashSet(configCandidates.size());
do {
StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
// 主要看这个方法
parser.parse(candidates);
parser.validate();
Set configClasses = new LinkedHashSet(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry());
}
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
processConfig.tag("classCount", () -> {
return String.valueOf(configClasses.size());
}).end();
candidates.clear();
if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set oldCandidateNames = new HashSet(Arrays.asList(candidateNames));
Set alreadyParsedClasses = new HashSet();
Iterator var13 = alreadyParsed.iterator();
while(var13.hasNext()) {
ConfigurationClass configurationClass = (ConfigurationClass)var13.next();
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
String[] var24 = newCandidateNames;
int var25 = newCandidateNames.length;
for(int var15 = 0; var15 < var25; ++var15) {
String candidateName = var24[var15];
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && !alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
} while(!candidates.isEmpty());
if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}
if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
((CachingMetadataReaderFactory)this.metadataReaderFactory).clearCache();
}
}
}
debug到org.springframework.context.annotation.ConfigurationClassParser#parse(org.springframework.core.type.AnnotationMetadata, java.lang.String)这个方法,我们看到对processConfigurationClass方法的调用,这是配置类加载的最重要的方法。
org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass
进入org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass这个方法
这里用ComponentScanAnnotationParser扫描了工程下面的spring容器要管理的类。然后关注下面这个蓝色行的方法
org.springframework.context.annotation.ConfigurationClassParser#getImports
可以看到,org.springframework.context.annotation.ConfigurationClassParser#collectImports
这个方法,在递归扫描所有类上的注解,把所有的@Import注解的value收集起来放到一个Set里面。
然后看一下这个@Import收集起来的集合,用在了org.springframework.context.annotation.ConfigurationClassParser#processImports
可以看到这里对文章上面讲的一些接口进行了扫描,并按照类型,调用接口方法,将所有普通的类收集起来,ImportSelector类会直接调用selectImports方法, deferredImportSelector会收集起来,等待后面processor方法里调用,ImportBeanDefinitionRegistrar类注册到configclass的register中。还在里面看到了各种aware接口的调用,org.springframework.context.annotation.ParserStrategyUtils#invokeAwareMethods。
parse方法运行完,进入process方法。org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorHandler#process
process方法,将spring.factories中配置的类,加载到了importstack中。如何读取spring.factories配置文件的呢,process方法中单独对deferredImports类进行了一次调用。这里看一下Sprint boot最重要的注解,@SpringBootApplication。这里面有三个重要的注解@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
这里只看@EnableAutoConfiguration注解,这里Import了一个类@Import({AutoConfigurationImportSelector.class})。AutoConfigurationImportSelector.class就是一个deferredImport。在org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#getAutoConfigurationEntry中的getCandidateConfigurations方法里,SpringFactoriesLoader.loadFactoryNames加载了classpath下所有的spring.factories文件
parse方法整个执行完,进入这个loadBeanDefinitions方法。
这里会调用上面整理的ImportBeanDefinitionRegistrar的接口,注册bean到spring 容器中。