本篇文章纯干货, 无废话, 觉得写得不错请点个赞支持一下;

SpringBoot启动流程

一定要配合这个流程图来看; 流程图重点关注 new SpringApplication 的部分即可

https://www.processon.com/view/link/667833be05675d325a7cf3fb?cid=65af5516f97995074a6638e9

详细介绍SpringBoot启动流程及配置类解析原理_springboot启动

重点关注带序号的部分

public ConfigurableApplicationContext run(String... args) {
    // 用于记录启动时间
	long startTime = System.nanoTime();
	DefaultBootstrapContext bootstrapContext = createBootstrapContext();
	ConfigurableApplicationContext context = null;
	// 配置headless属性,即“java.awt.headless”属性,默认为ture
	// 即使没有检测到显示器,也允许其启动.对于服务器来说,是不需要显示器的,所以要这样设置.
	configureHeadlessProperty();
    
    
	// [1] 通过getSpringFactoriesInstances从spring.factories配置文件中加载SpringApplicationRunListener对象并缓存;
	SpringApplicationRunListeners listeners = getRunListeners(args);

	// [2] 发布【ApplicationStartingEvent】事件;
    // 其实就是观察者模式, 遍历SpringApplicationRunListeners持有的所有Listener
    // 调用他们的 accept 方法, 将事件类型作为参数传进去, Listener 根据事件类型判断自己要不要处理;
	listeners.starting(bootstrapContext, this.mainApplicationClass);
    
	try {
		// 封装 String[] args 参数
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(
				args);
        
		// [3] 创建 Environment, 配置属性源
        // 包括读取系统变量,环境变量,命令行参数,默认变量以及配置文件(application.properties)等;
		// 注意这些属性源是有优先级的
		// 发布【ApplicationEnvironmentPreparedEvent】事件
		ConfigurableEnvironment environment = prepareEnvironment(listeners,
				applicationArguments);
        
		// 配置spring.beaninfo.ignore属性,默认为true,即跳过搜索BeanInfo classes.
		configureIgnoreBeanInfo(environment);
        
		// 控制台打印SpringBoot的bannner标志
		Banner printedBanner = printBanner(environment);
        
		// [4] 根据不同类型创建不同类型的spring applicationContext容器
		// servlet环境下,创建的是AnnotationConfigServletWebServerApplicationContext容器
		context = createApplicationContext();
		
		// [5] 为刚创建的ApplicationContext容器对象做一些初始化工作,准备一些容器属性值等;
        // 重点关注 应用初始化器的初始化方法, 添加BeanFactoryPostProcessor, 添加启动类BeanDefinition
		// 1)设置AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner
		// 2)postProcessApplicationContext, 对容器做一些相关的后置处理,实现类重写这个方法
		// 3)应用ApplicationContextInitializer的初始化方法,
		// 5)从context容器中获取beanFactory,并向beanFactory中注册一些必要的单例bean,比如applicationArguments,printedBanner(已经打印过的Banner)
        // 6) 添加两个BeanFactoryPostProcessor, 一个用于将Bean设置为默认懒加载
        // 一个用于调整 PropertySource 的顺序;
        // 7) 生成启动类的 BeanDefinition, 放到容器中;
		prepareContext(context, environment, listeners, applicationArguments,
				printedBanner);
		
        // [6] 刷新容器
		refreshContext(context);
		
        // [7] 执行刷新容器后的后置处理逻辑,注意这里为空方法, 是提供给SpringApplication子类的扩展
		afterRefresh(context, applicationArguments);
		
		// [8] 发布ApplicationStartedEvent事件,标志spring容器已经彻底准备好;
		listeners.started(context);
		
        // [9] 从容器中取出所有ApplicationRunner和CommandLineRunner
        // 应用他们的 run 方法, 先 ApplicationRunner 再CommandLineRunner 
        // 可以在这里进行业务逻辑的一些初始化操作
		callRunners(context, applicationArguments);
	}
	// ......
}
// refreshContext 来到 AbstractApplicationContext 的 refresh方法 
public void refresh(){
	// 刷新前做准备工作,比如初始化一些属性设置,属性合法性校验和保存容器中的一些早期事件等
	prepareRefresh();

	// [1] 从ApplicationContext获取 BeanFactory
	ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

	// [2] 对bean factory进行配置,配置bean factory的类加载器,后置处理器, 给beanFactory一些属性设置默认值
	prepareBeanFactory(beanFactory);

	// [3] 一个扩展点, 子类可以通过重写这个方法在BeanFactory准备完成后做进一步的设置;
	postProcessBeanFactory(beanFactory);
	
	StartupStep beanPostProcess = 
        this.applicationStartup.start("spring.context.beans.post-process");
	
	// [4] 执行所有 BeanFactoryPostProcessor 的 postProcess 方法
    // 里面有一个很重要的 ConfigurationClassPostProcessor
    // 它是一个 BeanDefinitionRegistryPostProcessor
    // 配置类就是在这里解析的, 这里会生成所有 Bean 的 BeanDefinition
    // 注意, 会先执行 BeanDefinitionRegistryPostProcessor(触发时机:bean定义注册之前)
    // 再执行 BeanFactoryPostProcessor(触发时机:bean定义注册之后bean实例化之前)
	invokeBeanFactoryPostProcessors(beanFactory);

	// [5] 实例化BeanPostProcessor, 添加到容器
	registerBeanPostProcessors(beanFactory);
	beanPostProcess.end();

	// 初始化国际化MessageSource相关的组件,比如消息绑定,消息解析等
	initMessageSource();

	// [6] 初始化事件广播器, 发布 ApplicationListner 所感兴趣的那些事件
	initApplicationEventMulticaster();

	// [7] 不同实现类有不同的实现, Servlet 应用环境下, 这里是创建内嵌的tomcat容器
	onRefresh();

	// [8] 向事件发布器注册实现了ApplicationListener接口的监听器
	registerListeners();

	// [9] 初始化所有剩余的单例bean。这一步非常重要,绝大部分的bean都在这里实例化与初始化, 
    // bean post processor会在这里调用。
	finishBeanFactoryInitialization(beanFactory);

	// 发布ContextRefreshedEvent事件
	finishRefresh();
}

配置类处理过程

pring Boot 会自动发现并注册所有在类路径下定义的 BeanFactoryPostProcessor,包括那些通过 @Configuration 类、@Component 扫描或直接在 META-INF/spring.factories 文件中定义的。

在 SpringBoot 启动的 Refresh 阶段, 对 BeanFactory 进行一些属性默认设置后, 就会来到 invokeBeanFactoryPostProcessors 方法, 这个方法会应用 BeanDefinitionRegistryPostProcessorBeanFacotryPostProcessor 的 postProcess 方法;

这其中就有专门处理配置类的ConfigurationClassPostProcessor;

它是一个BeanDefinitionRegistryPostProcessor;

配置类被处理后, 会被转换为一个ConfigurationClass对象, 这个对象保存了配置类的信息;

配置类分类

配置类可以分为两种, Full 和 Lite, @Configuration注解修饰的是 Full;

没有 @Configuration 注解修饰, 但有 @ComponentScan, @Import, @Bean方法的是 Lite;

他们都被认为是配置类, 都会被 ConfigurationClassPostProcessor 处理;

处理过程

  1. SpringBoot 应用启动时, 在 PrepareContext 阶段将启动类的 BeanDefinition 放到了容器中;
  2. 总的过程是: 以处理启动类为开头, 去不断添加新的 BeanDefinition, 找到新的配置类, 处理新的配置类; 从而完成对所有配置类的处理;
  3. 创建ConfigurationClassParser, 在一个循环中去处理启动类, 启动类可以配置多个, 循环去处理他们;
  4. 在循环内, 拿到待处理的一个启动类, 调用 parse 方法去处理这个配置类, 然后;
  • parse 方法内经过一系列调用, 先处理 @ComponentScan 注解, 进行包扫描, 将扫描得到的 BeanDefinition 保存到容器中; 如果是配置类的 BeanDefinition, 将直接调用 parse方法, 进行处理;
  • 继续说 parse 方法的处理逻辑: 处理 @Import 注解

parse 方法内调用 processImports 处理 @Import 注解,

  1. 遍历 @Import 注解引入的类对象;
  2. 如果是ImportBeanDefinitionRegister, 将其添加到ConfigurationClass对象中; 但不会向容器添加;
  3. 如果是ImportSelector对象, 会根据其返回的数组, 加载对应的类; 然后递归地调用 processImports 去处理这些类; 其本身不会向容器添加;
  4. 如果不是这两种类, 会将他作为一个配置类去处理; 所以 ImportSelector 返回的的数组对应的类会被添加到容器中;
  • 处理 @Bean 注解, 将 @Bean 注解修饰的方法生成一个对应的 BeanMethod , 添加到对应的ConfigurationClass中;
  • parse方法结束; 调用 ConfigurationClassBeanDefinitionReader::loadBeanDefinitions
  • 遍历这一轮生成的所有ConfigurationClass, 对其持有的所有ImportBeanDefinitionRegister, 调用他们的注册方法
  • 解析所有ConfigurationClass 的BeanMethod, 生成对应的 BeanDefinition, 添加到容器中;
  1. 没有新的要处理的启动类, 循环结束;
  2. 就这样处理完所有的配置类后, 会对 @Configuration 修饰的类, 进行 CGLIB 动态代理, 创建代理类, 代理 @Bean 注解修饰的方法; 但是创建代理类时, 仍会保留原本的目标类, 从而保证 AOP, 事务等功能能正常运行;

上文提到的属性加载与创建Bean过程详解在这里