Spring源码:SpringBoot启动流程分析

目录

一、演示代码

通过百度网盘分享的文件:SpringBoot启动流程代码.zip

链接:https://pan.baidu.com/s/1fnt5fWSBLY7UfFPoZFyZpQ?pwd=gyg1
提取码:gyg1

二、功能介绍

  • config包:
    • TestBeanLifeCycle类:测试Bean生命周期
    • TestConfiguration类:测试@Configuration
    • TestConfigurationProperties类:测试@ConfigurationProperties
    • TestDependsOn类:测试@DependsOn
    • TestImport类:测试@Import
    • TestImportResource类:测试@ImportResource
    • TestProfile类:测试@Profile
    • TestPropertySource类:测试@PropertySource
    • TestSuperClass类:测试普通父类中包含@Bean注解的方法
  • controller包:
    • TestController类:测试@Controller
  • service包>impl包:
    • TestServiceAImpl类:测试@Service、@Transactional
  • CodeStudyApplication类:测试@SpringBootApplication

三、代码分析

1、从主启动类中调用run()方法出发

在这里插入图片描述
按着Ctrl按键点击run()方法:

在这里插入图片描述

继续按着Ctrl按键点击run()方法:

在这里插入图片描述

2、看一下SpringApplication的构造方法在干什么?

继续按着Ctrl按键点击SpringApplication()构造方法:

在这里插入图片描述

继续按着Ctrl按键点击this()构造方法:

在这里插入图片描述

我们逐步来分析SpringApplication构造方法中具体在什么事情:

在这里插入图片描述

  • this.resourceLoader = resourceLoader:resourceLoader是null,所以赋值也是null

  • this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));:将主启动类放到primarySources集合中,后续会使用到,知道有这么回事就行,看到时再提一下

  • this.webApplicationType = WebApplicationType.deduceFromClasspath();:获取当前的web环境,即使用servlet还是reactive,我们使用servlet环境
    -

  • this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();:往bootstrapRegistryInitializers中放置了BootstrapRegistryInitializer类型的初始化器

  • setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));:往initializers中放置了ApplicationContextInitializer类型的初始化器

  • setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));:往listeners中放置了ApplicationListener类型的监听器;大家肯定注意到了,在上面两个设置初始化器的方法,以及设置监听器的方法中,也都涉及到了getSpringFactoriesInstances(XXX),我们来解释一下该方法的作用,按着Ctrl按键点进getSpringFactoriesInstances方法
    在这里插入图片描述
    在按着Ctrl按键点进getSpringFactoriesInstances方法
    在这里插入图片描述
    我们来看SpringFactoriesLoader.loadFactoryNames()方法,按着Ctrl按键点进loadFactoryNames方法
    在这里插入图片描述
    loadFactoryNames()方法目的是从所有的META-INF/spring.factories文件获取相关信息,然后组成一个Map集合,其中key是接口或者抽象类,而value是具体的实现类,大家来看下方法体中具体做了啥
    在这里插入图片描述
    我们往回看看loadFactoryNames()方法,其中loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList())作用就是从上面Map中根据传过来的接口/抽象类全名称获取实现类列表
    在这里插入图片描述
    我们往回看看createSpringFactoriesInstances()方法,当loadFactoryNames()方法执行完毕后,我们可以获得对应接口/抽象类的实现类全路径列表,那就该执行createSpringFactoriesInstances()方法了,这个方法就是通过反射方式依照实现类全路径列表来创建对象,这样我们就得到了所有的初始化器或者监听器
    在这里插入图片描述
    如果我们想添加一些相关类型的初始化器或者监听器,我们也可以在META-INF/spring.factories文件中添加相关内容,然后就会被Spring容器自动识别
    在这里插入图片描述
    拓展: 众所周知,@SpringBootApplication注解中的@ComponentScan注解只能扫描当前包以及子包下的注解,如果我们想开发一个项目,然后别人通过maven依赖引入之后就可以通过AOP注入我们编写的类,那我们的类需要被spring扫描到。我们可以在自己项目的resources文件夹下创建META-INF目录,然后在该目录下创建spring.factories文件,然后等号前面写EnableAutoConfiguration接口全路径,后面写需要被Spring管理的类的全路径,如下所示;至于这么做就可以让类被Spring管理的原因,那和主启动类上的@SpringBootApplication注解》@EnableAutoConfiguration注解》@Import(AutoConfigurationImportSelector.class)注解有关,我们进入AutoConfigurationImportSelector类,找到里面的selectImports方法,在看方法体内的getAutoConfigurationEntry方法,该方法可以找到spring.factories文件中所有实现了EnableAutoConfiguration接口的实现类,然后交给Spring进行管理;当然这个也涉及到使用主启动类上@ComponentScan注解扫描加有@Import注解的主启动类,从而将对象交给Spring进行管理
    在这里插入图片描述

  • this.mainApplicationClass = deduceMainApplicationClass();:将当前主启动类的全名称赋值给mainApplicationClass属性

3、看下run()方法的主要流程代码

SpringApplication类:
public ConfigurableApplicationContext run(String... args) {
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	DefaultBootstrapContext bootstrapContext = createBootstrapContext();
	ConfigurableApplicationContext context = null;
	configureHeadlessProperty();
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.starting(bootstrapContext, this.mainApplicationClass);
	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
		configureIgnoreBeanInfo(environment);
		Banner printedBanner = printBanner(environment);
		context = createApplicationContext();
		context.setApplicationStartup(this.applicationStartup);
		prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
		refreshContext(context);
		afterRefresh(context, applicationArguments);
		stopWatch.stop();
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
		}
		listeners.started(context);
		callRunners(context, applicationArguments);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, listeners);
		throw new IllegalStateException(ex);
	}

	try {
		listeners.running(context);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, null);
		throw new IllegalStateException(ex);
	}
	return context;
}

4、run():启动计时器计算springboot启动时间

在这里插入图片描述

5、run():创建DefaultBootstrapContext对象,并执行bootstrapRegistryInitializers集合中元素的初始化方法

在这里插入图片描述

6、run():设置无头服务

这里将java.awt.headless设置为true,表示运行在服务器端,在没有显示器器和鼠标键盘的模式下照样可以工作,模拟输入输出设备功能。

做了这样的操作后,SpringBoot想干什么呢?其实是想设置该应用程序,即使没有检测到显示器,也允许其启动.对于服务器来说,是不需要显示器的,所以要这样设置.

在这里插入图片描述

7、run():获取Spring启动运行的监听器,并发布开始启动监听事件

在这里插入图片描述

8、run():将main()方法中传递过来的参数封装成ApplicationArguments对象

在这里插入图片描述

9、run():准备environment对象,加载本地配置文件信息

在这里插入图片描述

我其实也没怎么看懂里面的代码,但是我看到了配置文件应该放置的位置是哪些,跟着我一步步往下走吧~

按着Ctrl点击prepareEnvironment()方法

在这里插入图片描述

按着Ctrl点击environmentPrepared()方法

在这里插入图片描述

其中doWithListeners()方法中参数2是一个消费型函数式接口,在该方法中会回调这个函数式接口,所以最终会执行environmentPrepared()方法,然后按着Ctrl + Alt点击environmentPrepared()方法:

在这里插入图片描述

按着Ctrl点击multicastEvent()方法

在这里插入图片描述

按着Ctrl点击multicastEvent()方法

在这里插入图片描述

按着Ctrl点击invokeListener()方法

在这里插入图片描述

按着Ctrl点击doInvokeListener()方法

在这里插入图片描述

按着Ctrl + Alt点击onApplicationEvent()方法,点击EnvironmentPostProcessorApplicationListener类:

在这里插入图片描述

按着Ctrl点击onApplicationEnvironmentPreparedEvent()方法

在这里插入图片描述

按着Ctrl + Alt点击postProcessEnvironment()方法,点击ConfigDataEnvironmentPostProcessor类:

在这里插入图片描述

按着Ctrl点击postProcessEnvironment()方法

在这里插入图片描述

按着Ctrl点击getConfigDataEnvironment()方法

在这里插入图片描述

点击ConfigDataEnvironment类:

在这里插入图片描述

在该类中,大家往上看下static静态代码块,可以看到配置文件可以放置的地方,分别是resources/config/resource/项目目录下/config/项目目录下/项目目录下/config目录下任意一个子目录/

在这里插入图片描述

我们可以把代码断点打到printBanner()

在这里插入图片描述

然后看下最终结果吧,打开environment看一下:

在这里插入图片描述

  • activeProfiles:对应配置文件中spring.profiles.active的值,即:
    在这里插入图片描述

  • defaultProfiles:默认就是default

  • propertySources:记录了配置文件的内容
    在这里插入图片描述
    最后一个记录了application.yml中的配置信息
    在这里插入图片描述
    倒数第2个记录了激活的配置文件中的信息:
    在这里插入图片描述

我们再看下environment中的属性propertySources的其他信息:

在这里插入图片描述

10、run():打印Banner图

按着Ctrl点击printBanner()方法

在这里插入图片描述

按着Ctrl点击print()方法

在这里插入图片描述

按着Ctrl点击getBanner()方法

在这里插入图片描述

可以看到下面有获取图片Banner图、文字Banner图、默认Banner图的入口:

在这里插入图片描述
我们先来看下图片Banner图的获取方式:

在这里插入图片描述

我们先来看下文字Banner图的获取方式

在这里插入图片描述

我们再来看下默认Banner图的获取方式:

在这里插入图片描述

通过上述方式,我们可以获取到Banner对象了,按着Ctrl + Alt点击printBanner()方法,我们就看下默认Banner图的实现方式吧,我们点击SpringBootBanner类:

在这里插入图片描述

可以看到对应代码和对应打印效果:

在这里插入图片描述

11、run():创建ApplicationContext对象

在这里插入图片描述

对于this.webApplicationType,我们在SpringBootApplication构造器中解释过,将使用servlet环境;而SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class, ApplicationContextFactory.class.getClassLoader())是从spring.factories文件中获取ApplicationContextFactory接口的实现类,我们将得到2个,分别是:

在这里插入图片描述
由于是for循环,所以都会处理到,按着Ctrl + Alt点进AnnotationConfigReactiveWebServerApplicationContext中:

在这里插入图片描述

由于当前是servlet环境,所以context返回值是null;接着按着Ctrl + Alt点进AnnotationConfigServletWebServerApplicationContext中,所以此时AnnotationConfigServletWebServerApplicationContext对象会被返回,然后赋值给context上下文

在这里插入图片描述

因此context = createApplicationContext();的最终结果是AnnotationConfigServletWebServerApplicationContext对象赋值给了context上下文对象

12、run():准备上下文

在这里插入图片描述

再看一下方法体中的细节:

在这里插入图片描述

比较有用的是最后一部分代码,也就是Load the sources,我们之前在2、看一下SpringApplication的构造方法在干什么?中提到过primarySources的赋值结果是主类信息,即CodeStudyApplication主类信息;此时按着Ctrl点击getAllSources()方法:

在这里插入图片描述

可以看到allSources中会被放入CodeStudyApplication主类信息:

在这里插入图片描述

按着Ctrl点击load()方法:

在这里插入图片描述

按着Ctrl点击load()方法:

在这里插入图片描述

按着Ctrl点击load()方法:

在这里插入图片描述

按着Ctrl点击load()方法:

在这里插入图片描述

按着Ctrl点击register()方法:

在这里插入图片描述

按着Ctrl点击registerBean()方法:

在这里插入图片描述

按着Ctrl点击doRegisterBean方法:

在这里插入图片描述

按着Ctrl + Alt点击registerBeanDefinition方法,选择GenericApplicationContext类:

在这里插入图片描述

按着Ctrl点击registerBeanDefinition方法:

在这里插入图片描述

将主启动类的信息放到beanDefinitionMapbeanDefinitionName

在这里插入图片描述

我们下面也会用到beanDefinitionName来从主启动类出发,进而扫描到所有的Bean类,到时候我们再提下这一章节

13、run():将bean交给Spring进行管理

13.1、先找到重要代码所在的位置

按着Ctrl点击refreshContext()方法:

在这里插入图片描述

按着Ctrl点击refresh()方法:

在这里插入图片描述

按着Ctrl + Alt点击refresh()方法,然后点击ServletWebServerApplicationContext类:

在这里插入图片描述

按着Ctrl点击refresh()方法,先来看下方法的全貌:

在这里插入图片描述

详细代码如下:

public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

		// Prepare this context for refreshing.
		prepareRefresh();

		// Tell the subclass to refresh the internal bean factory.
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

		// Prepare the bean factory for use in this context.
		prepareBeanFactory(beanFactory);

		try {
			// Allows post-processing of the bean factory in context subclasses.
			postProcessBeanFactory(beanFactory);

			StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
			// Invoke factory processors registered as beans in the context.
			invokeBeanFactoryPostProcessors(beanFactory);

			// Register bean processors that intercept bean creation.
			registerBeanPostProcessors(beanFactory);
			beanPostProcess.end();

			// Initialize message source for this context.
			initMessageSource();

			// Initialize event multicaster for this context.
			initApplicationEventMulticaster();

			// Initialize other special beans in specific context subclasses.
			onRefresh();

			// Check for listener beans and register them.
			registerListeners();

			// Instantiate all remaining (non-lazy-init) singletons.
			finishBeanFactoryInitialization(beanFactory);

			// Last step: publish corresponding event.
			finishRefresh();
		}

		catch (BeansException ex) {
			if (logger.isWarnEnabled()) {
				logger.warn("Exception encountered during context initialization - " +
						"cancelling refresh attempt: " + ex);
			}

			// Destroy already created singletons to avoid dangling resources.
			destroyBeans();

			// Reset 'active' flag.
			cancelRefresh(ex);

			// Propagate exception to caller.
			throw ex;
		}

		finally {
			// Reset common introspection caches in Spring's core, since we
			// might not ever need metadata for singleton beans anymore...
			resetCommonCaches();
			contextRefresh.end();
		}
	}
}

13.2、获取bean工厂

prepareRefresh()方法也没做什么事情,不过给earlyApplicationEvents赋值为new LinkedHashSet<>()还是挺棒的,用来记录由于循环依赖触发后被提前处理的bean信息,其他就没啥东西了,所以这个方法不在讲解。

下面直接说下obtainFreshBeanFactory(),目的是获得DefaultListableBeanFactory对象,该对象主要是和bean打交道用的,即 bean工厂

在这里插入图片描述

13.3、往bean工厂中填充 Bean后处理器、单例对象(环境类型)

在这里插入图片描述

关于后处理器,我没那么擅长,我们来看下那几个单例对象吧,后续我们有需要的话可以直接注入相关单例对象(通过@Resource注入后即可使用),然后直接取出配置文件或者环境信息中的内容使用

environment(即 ENVIRONMENT_BEAN_NAME 常量值):

在这里插入图片描述

systemProperties(即 SYSTEM_PROPERTIES_BEAN_NAME 常量值):

在这里插入图片描述

13.4、找到processConfigBeanDefinitions()解析方法执行的地方

首先看下postProcessBeanFactory(beanFactory)方法,它啥都没做

两个if判断都不符合要求,所以什么都没做

在这里插入图片描述

13.5、找到需要被Spring管理的类,即invokeBeanFactoryPostProcessors()方法(备菜过程)

注意: 当目录13.5执行完成的时候,所有的Bean信息(注意:是所有的)都会添加到DefaultListableBeanFactory对象的beanDefinitionMapbeanDefinitionName属性中,等待往SpringBean缓存中添加

现在我们出发寻找processConfigBeanDefinitions()方法。

我们回到run()方法的主要代码体,然后按着Ctrl点击invokeBeanFactoryPostProcessors()方法:

在这里插入图片描述

按着Ctrl点击invokeBeanFactoryPostProcessors方法:

在这里插入图片描述

先看第1个if方法:

我认为最有用的是BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;这一句,也就是将DefaultlistableBeanFactory赋值到registry

在这里插入图片描述

在看第2个if方法:

在这里插入图片描述

按着Ctrl点击invokeBeanDefinitionRegistryPostProcessors()方法:

在这里插入图片描述

按着Ctrl + Alt点击postProcessBeanDefinitionRegistry()方法,选择ConfigurationClassPostProcessor类(在上面第2张图可以看到postProcessors中只有1个对象,也就是ConfigurationClassPostProcessor对象):

在这里插入图片描述

按着Ctrl点击processConfigBeanDefinitions()方法:

在这里插入图片描述

通过registry.getBeanDefinitionNames()得到的包含主启动类信息,这个内容我们在12、run():准备上下文中已经说明了主启动信息是如何放入beanDifinitionNames中的;当处理之后configCandidates中存储的是主启动类信息,我们下面会从主启动类出发,进而扫描后找到所有的Bean类

在这里插入图片描述

依然在processConfigBeanDefinitions()方法中,我们往下滑动一下代码,找到parse()方法,按着Ctrl点击parse()方法:

在这里插入图片描述

13.5.1、分析添加@SpringBootApplication注解的主启动类(入口)

按着Ctrl点击parse()方法,现在bd对象就是主启动类的信息对象:

在这里插入图片描述

按着Ctrl点击processConfigurationClass()方法

在这里插入图片描述

重点:我们把话放在这里,这个方法将被多次递归调用到,我们根据流程一步步分析

第一个if判断,通过对类上添加的@Conditional注解以及派生注解进行分析,决定该类是否被舍弃

在这里插入图片描述

按着Ctrl点击doProcessConfigurationClass()方法:

在这里插入图片描述

doProcessConfigurationClass()方法中可以处理多个注解

13.5.1.1、处理携带@Component注解的类的内部类(不执行)

现在需要处理的是主启动类,其中主启动类上面添加@SpringBootApplication注解,我们点进@SpringBootConfiguration注解,在点击@Configuration注解,然后就可以看到@Component注解,那就符合doProcessConfigurationClass()方法中的第一个if判断:

在这里插入图片描述

这个if判断用来处理当前类的内部类中的相关注解,我们借用一下spring源码-Springboot解析配置类时,解析配置类的内部类中的代码,可以看到最终代码又回到了processConfigurationClass方法(重点:这个方法将被多次递归调用到,现在又被调用到了),既然回到了这个方法,那流程就是一样的了,我们不在重复分析了

private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter) throws IOException {
	//  获取当前配置类的所有内部类
	Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
	if (!memberClasses.isEmpty()) {
		List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
		for (SourceClass memberClass : memberClasses) {
		    // 1、内部类不是一个接口
		    // 2、内部类含有@Component、@ComponentScan、@Import、@ImportResource注解
		    // 3、内部类含有@Bean注解的方法
		    // 满足以上三个条件中的2或3即将当前内部类解析
			if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
					!memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
				candidates.add(memberClass);
			}
		}
		OrderComparator.sort(candidates);
		for (SourceClass candidate : candidates) {
		    // ImportStack是双端队列ArrayDeque的实现类,防止配置类被重复解析
			if (this.importStack.contains(configClass)) {
				this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
			}
			else {
				this.importStack.push(configClass);
				try {
				    // 解析内部类,见系列文章1的解析过程
					processConfigurationClass(candidate.asConfigClass(configClass), filter);
				}
				finally {
					this.importStack.pop();
				}
			}
		}
	}
}
13.5.1.2、处理@PropertySource注解(不执行)

回到doProcessConfigurationClass()主方法,现在需要处理的是主启动类,其中主启动类不包含@PropertySource注解,所以第二个if方法不会执行

在这里插入图片描述

13.5.1.3、处理@ComponentScan注解(一般情况下放在主启动类上,然后扫描到其他类,之后通过递归方式实现其他类的处理)

现在需要处理的是主启动类,其中主启动类上面添加@SpringBootApplication注解,点进去之后能看到@ComponentScan注解

首先shouldSkip()方法可以针对@Conditional以及派生注解进行是否处理逻辑判断,其中parse()方法可以对主启动类所在包中的类以及主启动类所在包的子包中的类进行扫描,按着Ctrl按键点击parse()方法

在这里插入图片描述

parse()方法体中基本都是获取注解信息的,一般情况下我们直接看最后一些代码片段接口,按着Ctrl点击doScan()方法:

在这里插入图片描述

先来看方法体中的findCandidateComponents()方法,该方法可以找到所有可以把当做Bean处理的类,按着Ctrl点击findCandidateComponents()方法:

在这里插入图片描述

按着Ctrl点击scanCandidateComponents()方法:

在这里插入图片描述

scanCandidateComponents()方法中可以看到resources中将存储相关包下所有的class文件信息:

在这里插入图片描述

根据以上原则,可以找到很多的类,包括项目中other包下的Test.class类,但是该类上没有任何注解,本来就是一个干扰类,Spring通过isCandidateComponent()方法来判断该类是否可以被Spring管理,按着Ctrl点击isCandidateComponent()方法:

在这里插入图片描述

其中includeFilters包含对@Component@ManagedBean注解的判断,只要添加这两个注解或者派生注解(比如@Configuration),那该类就可以被Spring当做Bean进行管理

在这里插入图片描述
这样我们就得到了findCandidateComponents()的返回值,即:主启动类所在包以及子包下被@Component注解或者派生注解修饰的类(@ManagedBean注解基本没用到)

我们继续回到doScan()方法,来看for循环方法是如何处理这些类的:

在这里插入图片描述

先来说一下for循环中进行注解值解析的代码,按着Ctrl点击processCommonDefinitionAnnotations()方法:

在这里插入图片描述

按着Ctrl点击processCommonDefinitionAnnotations()方法,我们可以看到代码是对@Lazy@Primary@DependsOn@Role@Description注解进行解析处理,然后往BeanDefinition candidate中填充对应属性,未来在真正处理该Bean的时候,这些都可以发挥作用,比如解析@DependsOn注解,那就需要提前将其他Bean放到Bean工厂中,然后才能把当前Bean放到Bean工厂中~

在这里插入图片描述

然后回到for循环中,其中for循环中最后一些代码比较重要,按着Ctrl点击registerBeanDefinition()方法:

在这里插入图片描述

按着Ctrl点击registerBeanDefinition方法:

在这里插入图片描述

按着Ctrl + Alt点击registerBeanDefinition方法,选择DefaultListableBeanFactory类:

在这里插入图片描述

其中hasBeanCreationStarted()方法结果是true,其中alreadyCreated集合在第一次处理主启动类的时候将会执行到,具体执行路径是:

  • org.springframework.context.support.AbstractApplicationContext#refresh
  • org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors
  • org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(org.springframework.beans.factory.config.ConfigurableListableBeanFactory, java.util.List<org.springframework.beans.factory.config.BeanFactoryPostProcessor>)
  • 看“First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered”下面的beanFactory.getBean(XXX)方法
  • org.springframework.beans.factory.support.AbstractBeanFactory#getBean(java.lang.String, java.lang.Class)
  • org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean
  • 之后找到“if (!typeCheckOnly)”的地方,然后观察“markBeanAsCreated(beanName)”
  • org.springframework.beans.factory.support.AbstractBeanFactory#markBeanAsCreated
  • 在该方法中就可以看到alreadyCreated被插入值的情况了

既然已经知道hasBeanCreationStarted()结果是true,那当前类的信息就可以被放在DefaultListableBeanFactory对象的beanDefinitionMapbeanDefinitionName属性中了

在这里插入图片描述

此时会将扫描出来的类信息组装成BeanDefinitionHolder对象,然后往beanDefinitions中填充,最后将beanDefinitions向上返回,我们看一下细节图:

在这里插入图片描述

我们将细节折叠起来,再来一个全景图:

在这里插入图片描述

然后最终会将扫描到的所有类信息一层层返回到parse()方法处,此时scannedBeanDefinitions就是扫描出来的类信息列表

在这里插入图片描述

我们往下看for循环,里面还有一个parse()方法;按着Ctrl点击parse()方法,此时又看到了我们的老朋友processConfigurationClass()方法(重点:这个方法将被多次递归调用到,现在又被调用到了),之前多次提到过,所以现在它就会通过递归形式对扫描到的类中的注解等信息进行处理

在这里插入图片描述

13.5.1.4、处理@Import注解

主启动类中确实有这个注解,具体是:@SpringBootApplication 》 @EnableAutoConfiguration 》 @Import@SpringBootApplication 》 @EnableAutoConfiguration 》 @@AutoConfigurationPackage》@Import

继续回到doProcessConfigurationClass()方法,然后看解析@Import注解的地方,本着执行顺序的原则,我们先来看getImports()方法,等会再回来看processImports()方法;按着Ctrl点击getImports()方法:

在这里插入图片描述

按着Ctrl点击collectImports()方法

在这里插入图片描述

通过递归的方式,一点点找到当前类上所有的@Import注解:

在这里插入图片描述

对于主启动类而言,获取到的imports如下:

在这里插入图片描述

现在getImports(sourceClass)执行完毕了,然后我们回到processImports()方法处,按着Ctrl按键点击processImports()方法:

在这里插入图片描述

进入processImports()方法之后,由于@Import里面可以放置3种对象,分别是实现ImportSelector接口、实现ImportBeanDefinitionRegistrar接口、普通类;

在启动类中,由于是@Import注解的value值是AutoConfigurationImportSelector,我们来看下它的类图:

在这里插入图片描述

因此我们选择实现ImportSelector接口的方式,因此candidate.isAssignable(ImportSelector.class)的结果是true

@SpringApplication中内部的注解是@Import(AutoConfigurationImportSelector.class),按着Ctrl点击AutoConfigurationImportSelector类,然后看selectImports()方法:

在这里插入图片描述

其中getAutoConfigurationEntry()方法用来获取需要被Spring管理的类,按着Ctrl点击该方法:

在这里插入图片描述

我们需要先获取需要被Spring管理的类信息,按着Ctrl点击getCandidateConfigurations()方法:

在这里插入图片描述

我们又看到了熟悉的SpringFactoriesLoader.loadFactoryNames()方法(解释:loadFactoryNames()方法目的是从所有的META-INF/spring.factories文件获取相关信息,然后从spring.factories文件中获取EnableAutoConfiguration接口的实现类)

在这里插入图片描述

这样getAutoConfigurationEntry()方法返回值autoConfigurationEntry中的configurations属性列表就是需要被Spring管理的类列表

在这里插入图片描述

我们debug来看一下最终结果吧:

在这里插入图片描述

之后递归调用processImports()方法,所以又回到了当前方法,但是需要被导入的类是一个普通类,所以将执行else分支

在这里插入图片描述
又回到了processConfigurationClass方法(重点:这个方法将被多次递归调用到,现在又被调用到了),既然回到了这个方法,那流程就是一样的了,我们不在重复分析了

13.5.1.5、处理@ImportResource注解(不执行)

回到doProcessConfigurationClass()主方法,现在需要处理的是主启动类,其中主启动类不包含@ImportResource注解,所以第4个if方法不会执行

在这里插入图片描述

13.5.1.6、处理@Bean注解(不执行)

回到doProcessConfigurationClass()主方法,现在需要处理的是主启动类,其中主启动类的方法上不包含@Bean注解,所以beanMethods是一个空集合,所以for循环不会执行

在这里插入图片描述

13.5.1.7、处理接口中默认方法上添加@Bean注解的情况(不执行)

回到doProcessConfigurationClass()主方法,现在需要处理的是主启动类,其中主启动类没有出现任何接口,所以该方法不会执行任何实际事情了

在这里插入图片描述

13.5.1.8、处理父类(不执行)

回到doProcessConfigurationClass()主方法,现在需要处理的是主启动类,由于主启动类没有父类,所以不会实际执行具体内容

在这里插入图片描述

13.5.2、处理添加@Bean注解的类

我们先来看下TestConfiguration.class

@Configuration
public class TestConfiguration {

    @Bean
    public A a() {
        return new A();
    }

    @Bean
    public B b() {
        return new B();
    }
}

我们回到doProcessConfigurationClass()方法,其中上面

在这里插入图片描述

上述方法执行之后,只是将携带@Bean注解的方法信息放在configClass对象的beanMethods属性中,等下也会将这些Bean放到beanDefinitionMap中,稍等片刻~

13.5.3、处理添加@Import注解的类

我们先来看下TestImport.class

在这里插入图片描述
我们回到doProcessConfigurationClass()方法,先来按着Ctrl点击getImports()方法

在这里插入图片描述

其中getImports()方法就是获取@Import注解中的value属性值

在这里插入图片描述

针对@Import注解中的几种情况,通过if判断后分别进行处理:

在这里插入图片描述
我们先来看情况1(实现ImportSelector接口):

在这里插入图片描述

我们再来看情况2(实现ImportBeanDefinitionRegistrar接口),其中情况2没有直接处理,而是把方法调用信息放在importBeanDefinitionRegistrars这个Map集合中,后续将会在this.reader.loadBeanDefinitions(configClasses);中调用到

在这里插入图片描述

我们再来看情况3(普通类),直接对普通类进行处理了,我们又看到了老朋友processConfigurationClass()方法,如果该类中存在添加@Bean的方法,也会被doProcessConfigurationClass()方法处理到,从而放到该类对应的ConfigurationClass对象的beanMethods属性中;目前只是将引入类的信息放在configurationClasses中,等下也会将这些Bean放到beanDefinitionMap中,稍等片刻~

在这里插入图片描述

13.5.4、处理添加@ImportResource注解的类

我们先来看下TestImportResource.class

在这里插入图片描述

我们回到doProcessConfigurationClass()方法,这种情况也没有直接处理,而是把方法调用信息放在importedResources这个Map集合中,后续将会在this.reader.loadBeanDefinitions(configClasses);中调用到

在这里插入图片描述

13.5.5、处理添加@PropertySource注解的类

我们先来看下TestPropertySource.class

在这里插入图片描述

我们回到doProcessConfigurationClass()方法,先来按着Ctrl点击processPropertySource()方法:

在这里插入图片描述

按着Ctrl点击processPropertySource()方法:

在这里插入图片描述

按着Ctrl点击addPropertySource()方法:

在这里插入图片描述

最终将配置信息添加到environment

13.5.6、处理不加注解的父类中添加@Bean注解的情况

我们先来看下TestSuperClass.classF.class

在这里插入图片描述

我们回到doProcessConfigurationClass()方法,由于类TestSuperClass的父类是类F,所以return可以返回类F,然后doProcessConfigurationClass()方法就返回了,此时sourceClass是类F,所以不为空,因此do...while循环再次启动;现在执行doProcessConfigurationClass()方法的是类F,然后对类F中添加@Bean注解的方法进行处理,后续将会在this.reader.loadBeanDefinitions(configClasses);中调用到,等下也会将这些类G放到beanDefinitionMap中,稍等片刻~

在这里插入图片描述

13.5.7、处理实现的接口中默认方法上添加@Bean注解的情况

我们先来看下TestServiceAImpl.classTestServiceA.class

在这里插入图片描述

我们回到doProcessConfigurationClass()方法,可以获取实现的接口的默认方法上添加@Bean注解的方法,我们将方法信息放在beanMethods属性中,后续将会在this.reader.loadBeanDefinitions(configClasses);中调用到,等下也会把相关信息放到beanDefinitionMap中,稍等片刻~

在这里插入图片描述

13.5.8、其他几种情况
  • 添加@Component注解的类中方法上添加@Bean注解也有效,请看:TestComponent.class
  • @Component注解有很多衍生注解,比如:@RestController(TestController.class)、@Service(TestServiceAImpl.class)、@Repository(TestRepository.class)
13.5.9、处理@Bean注解、@Import、@ImportResource注解而产生的bean信息,从而将它们加入到DefaultListableBeanFactory对象的beanDefinitionMap、beanDefinitionNames中

我们回到org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions方法

在这里插入图片描述

按着Ctrl点击loadBeanDefinitions()方法,该方法可以处理@Bean注解、@Import注解、@ImportResource注解而产生的bean信息

在这里插入图片描述

按着Ctrl点击loadBeanDefinitionsForConfigurationClass()方法:

在这里插入图片描述

根据这几种情况,根据代码情况来逐个分析:

在这里插入图片描述

现在依托示例代码给大家分别看一下这几种情况的真实数据:

我们可以把断点打到org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions方法中的如下位置:

this.reader.loadBeanDefinitions(configClasses);

我们可以看configClasses对象的内容,接下来请看结果:

情况1:

在这里插入图片描述

情况2:

在这里插入图片描述

情况3:

在这里插入图片描述

情况4:

在这里插入图片描述

接下来仔细分析一下代码执行过程吧~

情况1:使用@Import注解引入的普通类,以及实现ImportSelector接口的类引入的类

我们在上面分析过,使用@Import注解引入的实现ImportSelector接口的类引入的类最终也会走普通类的逻辑,所以我们就分析一个普通类放到DefaultListableBeanFactory对象的beanDefinitionMapbeanDefinitionNames属性的例子即可

在这里插入图片描述

可以把debug断点打到registerBeanDefinitionForImportedConfigurationClass方法上,按着Ctrl点击registerBeanDefinitionForImportedConfigurationClass()方法:

在这里插入图片描述

按着Ctrl + Alt点击registerBeanDefinition方法,选择DefaultListableBeanFactory类:

在这里插入图片描述

大家可以看到往DefaultListableBeanFactory对象的beanDefinitionMapbeanDefinitionNames属性中塞值的情况

在这里插入图片描述

情况2:使用@Bean注解引入的类

按着Ctrl点击loadBeanDefinitionsForBeanMethod方法:

在这里插入图片描述

我们仔细分析一下loadBeanDefinitionsForBeanMethod方法

1点是决定该方法是否会继续往下走,是否有成为被Spring管理的bean的可能性;也就是判断@Bean注解所在方法上的@Conditional注解或者衍生注解,比如@Profile@ConditionalOnProperty 注解
在这里插入图片描述
2点设置@Bean注解的属性上的初始化方法和销毁方法

在这里插入图片描述

3点调用registerBeanDefinition方法实现往DefaultListableBeanFactory对象的beanDefinitionMapbeanDefinitionNames属性中塞值,之前已经分析过了,这里不在分析

在这里插入图片描述

情况3:使用@ImportResource注解引入的配置文件

先来看一下我给的示例代码中使用@ImportResource注解的位置

在这里插入图片描述

按着Ctrl点击loadBeanDefinitionsFromImportedResources方法:

在这里插入图片描述

loadBeanDefinitionsFromImportedResources方法中,主要分为3个步骤进行处理:

首先获取readerClass对象:

在这里插入图片描述

其次就是获取reader对象

在这里插入图片描述

最后调用loadBeanDefinitions方法:

在这里插入图片描述

按着Ctrl + Alt点击上图的loadBeanDefinitions方法,然后按着Ctrl继续点击重载的loadBeanDefinitions方法:

在这里插入图片描述

按着Ctrl继续点击loadBeanDefinitions方法:

在这里插入图片描述

按着Ctrl继续点击loadBeanDefinitions方法:

在这里插入图片描述

按着Ctrl + Alt继续点击loadBeanDefinitions方法,选择XmlBeanDefinitionReader类:

在这里插入图片描述

按着Ctrl继续点击loadBeanDefinitions方法:

在这里插入图片描述

再往下的代码执行路径就太长了,大家感兴趣的往下追一下吧,也不难找~

情况4:执行@Import注解引入的视线ImportBeanDefinitionRegistrar接口的类中的方法

按着Ctrl继续点击loadBeanDefinitionsFromRegistrars方法:

在这里插入图片描述

按着Ctrl继续点击registerBeanDefinitions方法:

在这里插入图片描述

按着Ctrl + Alt继续点击registerBeanDefinitions方法,点击我们示例代码中编写的实现类MyImportRegistry,然后就可以执行对应的registerBeanDefinitions方法了

在这里插入图片描述

13.6、注册Bean后处理器

按着Ctrl点击registerBeanPostProcessors()方法:
在这里插入图片描述

按着Ctrl点击registerBeanPostProcessors方法:

在这里插入图片描述

首先在registerBeanPostProcessors方法根据BeanPostProcessor类型查找后续处理器名称列表,其实这个查询方式和之前DefaultListableBeanFactory对象的beanDefinitionNames属性有关

在这里插入图片描述

然后根据BeanPostProcessor实现类是否实现PriorityOrdered接口(Order接口的子接口)、是否实现Ordered接口,以及实现PriorityOrdered接口and属于MergedBeanDefinitionPostProcessor接口这几种判断方式组成的if...else逻辑,从而完成BeanPostProcessor实现类的分组

在这里插入图片描述
然后针对识别出来的不同集合进行逐次处理

我们来看下priorityOrderedPostProcessors集合,也就是实现PriorityOrdered接口(Order接口的子接口)的集合,按着Ctrl点击sortPostProcessors方法:

在这里插入图片描述

虽然comparatorToUseAnnotationAwareOrderComparator对象,但是该对象所属的类是类OrderComparator的子类,真正的compare比较方法其实在OrderComparator类中,所以我们直接点击下图中OrderComparator类即可

在这里插入图片描述

一起去看下OrderComparator类中的compare方法:

在这里插入图片描述

然后来看registerBeanPostProcessors方法,然后将排序之后的priorityOrderedPostProcessorsbeanPostProcessors集合中放置,由于是升序排序,所以越小在beanPostProcessors集合中越靠前,到时候越先执行

在这里插入图片描述

后面依次将实现Order接口的BeanPostProcessor实现类升序排序后,然后往beanPostProcessors集合中放置,由于是升序排序,所以越小在beanPostProcessors集合中越靠前,到时候越先执行

在这里插入图片描述

然后依次执行剩余2种情况,最终实现将bean后处理器填充到beanPostProcessors集合中

在这里插入图片描述

13.7、创建并启动Tomcat服务器,即onRefresh()方法

按着Ctrl + Alt点击onRefresh()方法,选择ServletWebServerApplicationContext类:
在这里插入图片描述

按着Ctrl点击createWebServer方法:

在这里插入图片描述

获取TomcatServletWebServerFactory对象:

在这里插入图片描述

按着Ctrl + Alt点击getWebServer方法,然后选中TomcatServletWebServerFactory类:

在这里插入图片描述

首先创建Tomat对象:

在这里插入图片描述
然后启动Tomcat对象:

在这里插入图片描述

13.8、将bean交给Spring进行管理,即finishBeanFactoryInitialization方法(做菜过程)

13.8.1、找到getBean方法和doGetBean方法

按着Ctrl点击finishBeanFactoryInitialization方法:

在这里插入图片描述

按着Ctrl + Alt点击finishBeanFactoryInitialization方法:

在这里插入图片描述

按着Ctrl点击getBean()方法:

在这里插入图片描述

按着Ctrl点击doGetBean()方法:

在这里插入图片描述

13.8.2、简单分析一下getSingleton()方法和@DependsOn注解的作用
13.8.2.1、优先讲解一下getSingleton()方法在解决循环依赖和动态代理时起到的作用
  • 如果存在2个类,分别是A类、B类,B依赖A
    • 当获取A对象的时候,由于A类不需要注入其他类,所以很方便的将生成对象放到一级缓存singletonObjects
    • 当获取B对象的时候,需要先注入A对象,B对象在获取A对象时需要执行到下面getSingleton方法,然后在getSingleton方法中会从一级缓存中取出A类的最终对象
  • 如果存在4个类,分别是A类、B类、C类、D类,A依赖B、C,B也依赖A,C也依赖A,D也依赖A
    • 当获取A对象的时候,此时创建A对象的信息(匿名内部类)存在三级缓存singletonFactories中,然后注入B对象
    • 而获取B对象的时候,需要先注入A对象
    • B对象在获取A对象时需要执行到下面getSingleton方法,然后在getSingleton方法中会执行三级缓存中对象A的匿名内部类的方法,从而获取A类的普通对象/代理对象(存在需要代理的情况时)
    • 之后将A类的普通对象/代理对象(存在需要代理的情况时)放到二级缓存earlySingletonObjects
    • 当获取A对象的时候,需要先注入C对象,而获取C对象的时候,需要先注入A对象
    • C对象在获取A对象时需要执行到下面getSingleton方法,然后在getSingleton方法中会从二级缓存中取出A类的普通对象/代理对象(存在需要代理的情况时)
    • 这时候A对象注入了B对象和C对象,之后将二级缓存中A类的普通对象/代理对象(存在需要代理的情况时)放到一级缓存singletonObjects中,也就是将A类的最终对象放到一级缓存中
    • 当获取D对象的时候,需要先注入A对象,D对象在获取A对象时需要执行到下面getSingleton方法,然后在getSingleton方法中会从一级缓存中取出A类的最终对象

在这里插入图片描述

13.8.2.2、进入getSingleton()方法内部探究一下

按着Ctrl点击getSingleton()方法:

在这里插入图片描述

按着Ctrl点击getSingleton()方法:

在这里插入图片描述

下面是getSingleton()方法的细节:

在这里插入图片描述

13.8.2.3、讲一下@DependsOn注解的作用

先来看一下示例代码,这个注解的作用是让TestDependsOn对象在testComponent对象之后被Spring管理,这种一种类依赖的解耦方式,比如只有testComponent对象的初始化方法执行完成之后,才能执行TestDependsOn对象的初始化方法,那我们就需要这个注解来做事情

在这里插入图片描述

承接之前的代码,最开始时,代码Object sharedInstance = getSingleton(beanName);的执行结果肯定是null,所以一定是执行else逻辑

在这里插入图片描述

else逻辑中,有这样一些代码,可以看到首先通过getDependsOn()方法获取到依赖对象信息,然后最终调用getBean方法先获取依赖对象,从而让依赖对象首先被Spring当做Bean管理,这就完成了上面所说的启动顺序要求,其中getBean()方法再往下就不分析了,这又是一个同样的递归逻辑

在这里插入图片描述

13.8.3、通过截图方式来分析不存在循环依赖的情况
13.8.3.1、背景

如果存在2个类,分别是A类、B类,B依赖A,但是A不依赖B

13.8.3.2、将对象A放入一级缓存中(不想写那么多了,写一点点吧~)

大家感兴趣的请看我的另外一篇文章Sping源码:三级缓存,这篇文章的截图写的较为详细~

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

13.8.4、通过截图方式分析存在循环依赖的情况
13.8.4.1、背景

如果存在4个类,分别是A类、B类、C类、D类,A依赖B、C,B也依赖A,C也依赖A,D也依赖A

13.8.4.2、实在不想在写了,确实太多了

大家感兴趣的请看我的另外一篇文章Sping源码:三级缓存,这篇文章的截图写的较为详细~

13.8.5、Bean实例化和初始化

按着Ctrl + Alt点击createBean方法:

在这里插入图片描述

按着Ctrl + Alt点击doCreateBean方法:

在这里插入图片描述

按着Ctrl点击initializeBean方法:

在这里插入图片描述

分析一下initializeBean方法:

在这里插入图片描述
我们来详细分析一下具体过程

先来看一下示例代码截图:

在这里插入图片描述

开始一步一步分析:

1、TestBeanLifeCycle第1步:无参构造~

在这里插入图片描述

2、TestBeanLifeCycle第2步:实现3个接口,等待重写方法被调用……

依旧从doCreateBean方法出发:

在这里插入图片描述

按着Ctrl点击invokeAwareMethods方法:

在这里插入图片描述

分析下invokeAwareMethods方法细节,我们正好实现了这3个Aware相关接口,所以这3个接口会被调用~

在这里插入图片描述

3、TestBeanLifeCycle第3步:@PostConstruct 注解

依旧从doCreateBean方法出发:

在这里插入图片描述

按着Ctrl点击applyBeanPostProcessorsBeforeInitialization方法:

在这里插入图片描述

按着Ctrl + Alt点击postProcessBeforeInitialization方法,选择InitDestroyAnnotationBeanPostProcessor类:

在这里插入图片描述

按着Ctrl点击invokeInitMethods方法:

在这里插入图片描述

实现对添加@PostConstruct注解的方法调用:

在这里插入图片描述
4、TestBeanLifeCycle第4步:InitializingBean 接口的 afterPropertiesSet 方法执行了

依旧从doCreateBean方法出发:

在这里插入图片描述

按着Ctrl点击invokeInitMethods方法:

在这里插入图片描述

分析代码后可以看到调用实现InitializingBean接口后重写的afterPropertiesSet方法:

在这里插入图片描述
5、TestBeanLifeCycle第5步:@Bean注解的initMethod属性~

依旧从doCreateBean方法出发:

在这里插入图片描述

按着Ctrl点击invokeInitMethods方法:

在这里插入图片描述

分析代码后可以看到调用实现InitializingBean接口后重写的afterPropertiesSet方法:

在这里插入图片描述

分析一下自定义的初始化方法,其中这种方式和在xml配置文件中配置的方式一致:

在这里插入图片描述

13.8.5、Bean销毁

在这里插入图片描述

13.8.6、Bean的生命周期

目录13.8.5、Bean实例化和初始化13.8.5、Bean销毁组合到一起就是Bean的生命周期

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值