SpringBoot源码解析-启动流程(二)

上一篇SpringBoot源码解析-启动流程(一)我们分析了SpringBoot启动的第一步:创建SpringApplication对象,这一篇我们继续分析其中的第二步:run方法,在开始之前,我们先有个宏观的认识。
在这里插入图片描述

我们进入run方法:

    public ConfigurableApplicationContext run(String... args) {
        //创建计时器
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        //创建空的IOC容器,和一组异常报告器
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        //配置与awt相关的信息
        configureHeadlessProperty();
        //获取SpringApplicationRunListeners
        SpringApplicationRunListeners listeners = getRunListeners(args);
        //调用监听器starting方法
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //准备运行时环境
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);
            //打印SpringBoot的banner
            Banner printedBanner = printBanner(environment);
            //创建ApplicationContext
            context = createApplicationContext();
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context);
            //初始化IOC容器
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            //刷新IOC容器
            refreshContext(context);
            //刷新后的处理
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            //发布started事件
            listeners.started(context);
            //运行期回调
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }
        try {
            //发布running事件
            listeners.running(context);
        }catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

下面我们进行具体的分析

创建StopWatch对象

StopWatch是位于org.springframework.util包下的一个工具类,通过它可方便的对程序部分代码进行计时(ms级别),我们平时如果要统计代码执行时间也可以使用它,例如:

public static void main(String[] args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start("业务1");
        //业务1的代码
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        stopWatch.stop();
        stopWatch.start("业务2");
        //业务2的代码
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        stopWatch.stop();
        System.out.println(stopWatch.prettyPrint());
    }
	执行结果如下:
	---------------------------------------------
	ns         %     Task name
	---------------------------------------------
	2999784000  038%  业务1
	4999352800  062%  业务2
创建空的IOC容器,和一组异常报告器
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
获取SpringApplicationRunListeners

我们进入方法,这边调用了getSpringFactoriesInstances方法,主要初始化一组SpringApplicationRunListeners。SpringApplicationRunListeners是SpringBoot在启动过程中发布各种事件用的。

	private SpringApplicationRunListeners getRunListeners(String[] args) {
		Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
		return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
	}
准备运行时环境(prepareEnvironment)

我们先了解一下 Environment ,它是IOC容器的运行环境,它包括Profile和Properties两大部分。它封装了诸多属性来源(命令行属性、JVM properties、system环境变量、properties文件等)。

	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments) {
		// 创建运行时环境
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		// 配置运行时环境
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		ConfigurationPropertySources.attach(environment);
		// 发布environmentPrepared事件
		listeners.environmentPrepared(environment);
		// 环境与应用绑定
		bindToSpringApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}

环境准备好后,我们通过debug可以看到初始化了如下属性来源:
在这里插入图片描述

打印SpringBoot的banner

还记得我们启动完SpringBoot应用后,在控制台打印了如下字符:
在这里插入图片描述
就是通过printBanner这个方法实现的,这边我们不多深究。

创建ApplicationContext(createApplicationContext)

我们进入createApplicationContext方法:

	public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context.annotation.AnnotationConfigApplicationContext";
	public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
	public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
	protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
				// 根据Web应用类型决定实例化哪个IOC容器
				switch (this.webApplicationType) {
				case SERVLET:
					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
			}
		}
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}

我们这边是Servlet环境,故上面导入的类为:AnnotationConfigServletWebServerApplicationContext 。

初始化IOC容器(prepareContext)
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
        SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    // 将创建好的应用环境设置到IOC容器中
    context.setEnvironment(environment);
    // IOC容器的后置处理
    postProcessApplicationContext(context);
    // 执行初始化器Initializer
    applyInitializers(context);
    // 发布contextPrepared方法事件
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    // 创建spring bean容器
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    // 注册两个组件springApplicationArguments和springBootBanner
 	beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
    if (printedBanner != null) {
        beanFactory.registerSingleton("springBootBanner", printedBanner);
    }
    if (beanFactory instanceof DefaultListableBeanFactory) {
        ((DefaultListableBeanFactory) beanFactory)
                .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
    }
    // 加载主启动类
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    // 主启动类注册至IOC容器
    load(context, sources.toArray(new Object[0]));
    // 发布contextLoaded事件
    listeners.contextLoaded(context);
}

我们主要看下最后几行

	// 加载主启动类
    Set<Object> sources = getAllSources();

这边加载了我们的启动类,也就是main方法所在的类,通过debug可以验证:
在这里插入图片描述

	// 主启动类注册至IOC容器
    load(context, sources.toArray(new Object[0]));

这个方法主要就是把我们的main主启动类以BeanDefinition注入到IOC容器中,执行完通过debug可以验证:
在这里插入图片描述

刷新IOC容器(refreshContext)

这个方法其实是调用了AbstractApplicationContext的refresh方法,这个方法也是Spring Ioc容器最核心方法,这边我们只展示一下方法,以后再单独做详细分析。

   // 最终调用AbstractApplicationContext的refresh方法
   public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
        	// 初始化前的预处理
            this.prepareRefresh();
            // 获取BeanFactory,加载所有bean的定义信息
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            // BeanFactory的预处理
            this.prepareBeanFactory(beanFactory);

            try {
            	// 初始化BeanFactory的后置处理器
                this.postProcessBeanFactory(beanFactory);
                // 执行BeanFactory的后置处理器
                this.invokeBeanFactoryPostProcessors(beanFactory);
                // 注册Bean的后置处理器
                this.registerBeanPostProcessors(beanFactory);
                //  初始化MessageSource
                this.initMessageSource();
                // 初始化事件派发器
                this.initApplicationEventMulticaster();
                // 供子类的扩展
                this.onRefresh();
                // 注册监听器
                this.registerListeners();
                // 实例化所有单例Bean
                this.finishBeanFactoryInitialization(beanFactory);
                // 完成容器的创建工作
                this.finishRefresh();
            } catch (BeansException var9) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }
刷新后的处理(afterRefresh)

这是个空方法,故不再深究。

发布started事件

这边代码比较简单,主要是发布started事件。

	void started(ConfigurableApplicationContext context) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.started(context);
		}
	}

至此,SpringBoot启动流程的第二部分run方法讲完了,这边只是大概梳理了一下整体流程,一些内部实现细节没有较深入分析,感兴趣的朋友可以自己深入研究。

SpringBoot源码解析-启动流程(一)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值