springboot 版本 2.5.5,blog内容都是个人理解。欢迎留言讨论~
Debug路线图
说多都是泪,大家看图。
让我们从run说起
用了这么多年的的Springboot,这个 run() 方法到底做了些什么事呢?
@SpringBootApplication
public class SpringbootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemoApplication.class, args);
}
}
归属
run() 方法归属于 SpringApplication.class 对象,所以在调用run() 方法前,需要先实例化 SpringApplication.class 对象:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
SpringApplication.class 对象实例化时,都做了些什么事呢? 这里主要看需要注意两个方法:①getSpringFactoriesInstances() 和 ②deduceMainApplicationClass()
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
①getSpringFactoriesInstances() 方法主要加载整个应用程序中的 spring.factories 文件,将文件的内容放到缓存对象中,方便后续获取使用。
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
result = new HashMap<>();
try {
Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
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();
String[] factoryImplementationNames =
StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
for (String factoryImplementationName : factoryImplementationNames) {
result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
.add(factoryImplementationName.trim());
}
}
}
result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
cache.put(classLoader, result);
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
return result;
}
②deduceMainApplicationClass() 方法返回了启动类的类信息:
总结
实例化SpringApplication.class 对象完成了两件事:
1.加载整个应用程序中的 spring.factories 文件,将文件的内容放到缓存对象中,方便后续获取使用。 2.返回了启动类的类信息。
run
有了SpringApplication.class 对象实例对象,接下来就可以调用run() 方法。
在run() 方法中,我们主要关注两个方法prepareContext() 和 refreshContext()
public ConfigurableApplicationContext run(String... args) {
try {
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
}
prepareContext() 方法准备上下文环境,通过调用load() 方法加载启动类,为获取启动类上的注解做准备;
private void load(Class<?> source) {
if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
((GroovyBeanDefinitionReader) this.groovyReader).beans(loader.getBeans());
}
if (isEligible(source)) {
this.annotatedReader.register(source);
}
}
refreshContext() 方法最终调用了AbstractApplicationContext.class 类的 refresh() ,这里相信看过spring源码的小伙伴都很熟悉 refresh() 这个方法。
自动装配操作的主战场主要是在 ①invokeBeanFactoryPostProcessors() 方法,①调用了②invokeBeanDefinitionRegistryPostProcessors() 方法,②调用了ConfigurationClassPostProcessor.class 的③postProcessBeanDefinitionRegistry() 方法,③调用了 ④processConfigBeanDefinitions() 方法;
④ processConfigBeanDefinitions() 方法中:
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
if (logger.isDebugEnabled()) {
logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
}
}
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
parser.parse(candidates);
parser.validate();
}
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
}
this.deferredImportSelectorHandler.process();
}
进入判断后parse() 方法会接着调用 ①processConfigurationClass() 方法,①调用②doProcessConfigurationClass() 方法;
②doProcessConfigurationClass() 中又开始对注解进行进一步的解析,包括@PropertySource、@ComponentScan、@Import(咱们看这个)、@ImportResource、@Bean,解析之前,会通过getImports() 方法调用collectImports() 方法,统计出被@Import标注的类型信息;
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
Set<SourceClass> imports = new LinkedHashSet<>();
Set<SourceClass> visited = new LinkedHashSet<>();
collectImports(sourceClass, imports, visited);
return imports;
}
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
throws IOException {
if (visited.add(sourceClass)) {
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
if (!annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
}
}
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
}
getImports() 方法查询结果展示:
当parse() 方法解析完成 @Import 注解后(这里忘记的小伙伴,可看看上面的parse()方法,我有代码注释),接着开始调用①process() 方法,①中调用②processGroupImports() 方法,②中接着调用 ③grouping.getImports() 方法,③调用DeferredImportSelector.Group 接口的 ④process() 方法,这里我们看它的实现类 AutoConfigurationImportSelector.class 实现的 ④process() 方法(这里需要留意一下,一会还会回来用到),④调用了 ⑤getAutoConfigurationEntry() ,⑤中调用了⑥getCandidateConfigurations() 方法;
重点来了:经过上面的一系列方法调用,终于来到这个方法,相信大家在许多博客里都又看到过 ⑥getCandidateConfigurations() 这个方法,但又没有说清楚具体是怎么调用到这个方法的,只是说了这个类会得到待配置的class的类名集合等等;
在⑥这个方法中,显示通过 ⑦getSpringFactoriesLoaderFactoryClass() 这个方法返回了一个EnableAutoConfiguration.class 注解对象,然后又通过调用 ⑧loadFactoryNames() ,⑧又调用了 ⑨loadSpringFactories() ;
⑧⑨方法看着是不是比较眼熟? 是的,我们在初始化SpringApplication对象时,曾调用过这两个方法,在调用⑨时,将 spring.factories 文件的内容放到cache缓存对象中。
@Override
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;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = cache.get(classLoader);
if (result != null) {
return result;
}
此时的cache对象中存在EnableAutoConfiguration对象,size=131个: 这131个就是 spring.factories 文件中的自动装配配置项: 当然,这里面有许多我们没有用到类信息也被装配了进来,这里不要着急接着往下看,装配完成后回到了⑤getAutoConfigurationEntry() 方法中,且返回了一个List< String>的一个配置类信息集合,接着又做了些什么事? 从上图可以看出,131个配置信息,经过过滤移除后,最终变成13个需要使用的,拿到最终配置信息,(愣着干嘛,赶紧撒花呀!),到这里自动装配过程基本上就结束了。
这里的结束是指自动装配过程结束,也就是我们 refresh() 中的invokeBeanFactoryPostProcessors() 方法执行结束,当然这个方法还做很多别的事,但是本文只关注自动装配相关,完成此方法后并不表示类就已经实例化完成,这里只是将类信息装配到了spring容器中,后续会有别的方法完成类的实例化。(实例化看它:finishBeanFactoryInitialization())
再说说注解
@SpringBootApplication 是的没错,这个注解大家都熟悉,springboot 项目启动类上都有: @SpringBootApplication 中包含了两个注解:
@EnableAutoConfiguration (重点):启用 SpringBoot 的自动配置机制;@ComponentScan : 扫描被@Component (@Service ,@Controller )注解的 bean,注解默认会扫描该类所在的包下所有的类;@SpringBootConfiguration :允许在上下文中注册额外的 bean 或导入其他配置类;
三个注解中,自动装配的核心 @EnableAutoConfiguration 就是这个注解: @EnableAutoConfiguration 注解通过 Spring 提供的 @Import 注解导入了 AutoConfigurationImportSelector.class 类(@Import 注解可以导入配置类或者 Bean 到当前类中),这个类的作用在上也说过(获取spring.factories 文件中待配置的class的类名集合)。
感谢各位大佬的:点赞、收藏和评论!
|