27.2.从零开始学springboot-运行原理

前言

上章介绍了springboot入口类SpringApplication类的初始化流程,本章,我们分析介绍SpringApplication.run()方法的运行流程及原理。

SpringApplication实例run方法运行原理分析

public ConfigurableApplicationContext run(String... args) {
    //1.创建并启动计时监控类
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    //2.初始化应用上下文和异常报告集合
    ConfigurableApplicationContext context = null;
    //异常通知者列表
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    //3.设置系统配置java.awt.headless
    configureHeadlessProperty();
    //4.创建所有 Spring 运行监听器并发布应用启动事件
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        //5.初始化应用启动参数
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        //6.根据运行监听器和应用参数来准备Spring环境变量
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        configureIgnoreBeanInfo(environment);
        //7.创建 Banner 打印类
        Banner printedBanner = printBanner(environment);
        //8.创建应用上下文
        context = createApplicationContext();
        //9.获取Spring异常报告者Bean列表
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        //10.准备应用上下文
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        //11.刷新应用上下文
        refreshContext(context);
        //12.应用上下文刷新后置处理
        afterRefresh(context, applicationArguments);
        //13.停止计时监控类
        stopWatch.stop();
        //14.输出日志记录执行主类名、时间信息
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        //15.发布应用上下文启动完成事件
        listeners.started(context);
        //16.执行所有 Runner 运行器
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        //处理运行异常
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        //17.发布应用上下文就绪事件
        listeners.running(context);
    }
    catch (Throwable ex) {
        //处理运行异常
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    //18.返回应用上下文
    return context;
}

run方法比较复杂,我们一步一步分析。
首先看第1步
创建并启动计时监控类

StopWatch stopWatch = new StopWatch();
stopWatch.start();

跳转到StopWatch来看下该类的实现

public void start() throws IllegalStateException {
    start("");
}

public void start(String taskName) throws IllegalStateException {
    if (this.currentTaskName != null) {
        throw new IllegalStateException("Can't start StopWatch: it's already running");
    }
    this.currentTaskName = taskName;
    this.startTimeMillis = System.currentTimeMillis();
}

可以看出,start()首先记录了当前任务的名称,默认为空字符串,然后记录当前 Spring Boot 应用启动的开始时间。

第2步
初始化应用上下文和异常报告集合

ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

再看第3步
设置系统属性 java.awt.headless 的值

configureHeadlessProperty();

设置该默认值为:true,表示运行在服务器端,在没有显示器和鼠标键盘的模式下工作,模拟输入输出设备功能。说白了就是当检测没有显示器和键盘鼠标时,也运行程序启动。

第4步
创建所有 Spring 运行监听器并发布应用启动事件

SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();

我们看下getRunListeners()的实现

private SpringApplicationRunListeners getRunListeners(String[] args) {
    Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
    return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
            SpringApplicationRunListener.class, types, this, args));
}

SpringApplicationRunListeners(Log log,
        Collection<? extends SpringApplicationRunListener> listeners) {
    this.log = log;
    this.listeners = new ArrayList<>(listeners);
}

创建逻辑和之前实例化初始化器和监听器的一样,一样调用的是 getSpringFactoriesInstances 方法来获取配置的监听器名称并实例化所有的类。
SpringApplicationRunListener 所有监听器配置在 spring-boot-2.1.4.RELEASE.jar!/META-INF/spring.factories 这个配置文件里面。

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

第5步
初始化默认应用参数类

ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);

第6步
根据运行监听器和应用参数来准备 Spring 环境

ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
configureIgnoreBeanInfo(environment);

我们看下prepareEnvironment()的实现

private ConfigurableEnvironment prepareEnvironment(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    //获取/创建应用环境
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    //配置应用环境
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    listeners.environmentPrepared(environment);
    bindToSpringApplication(environment);
    if (!this.isCustomEnvironment) {
        environment = new EnvironmentConverter(getClassLoader())
                .convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
    }
    ConfigurationPropertySources.attach(environment);
    return environment;
}


//获取/创建应用环境,这里分为标准 Servlet 环境和标准环境。
private ConfigurableEnvironment getOrCreateEnvironment() {
    if (this.environment != null) {
        return this.environment;
    }
    switch (this.webApplicationType) {
    case SERVLET:
        return new StandardServletEnvironment();
    case REACTIVE:
        return new StandardReactiveWebEnvironment();
    default:
        return new StandardEnvironment();
    }
}


//配置应用环境
protected void configureEnvironment(ConfigurableEnvironment environment,
        String[] args) {
    if (this.addConversionService) {
        ConversionService conversionService = ApplicationConversionService
                .getSharedInstance();
        environment.setConversionService(
                (ConfigurableConversionService) conversionService);
    }
    //1.配置 property sources
    configurePropertySources(environment, args);
    //2.配置 Profiles
    configureProfiles(environment, args);
}

第7步
创建 Banner 打印类

Banner printedBanner = printBanner(environment);

第8步
创建应用上下文

context = createApplicationContext();

我们继续看下createApplicationContext()的实现,其实就是根据不同的应用类型初始化不同的上下文应用类。

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            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);
}

第9步
准备异常报告器

exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);

逻辑和之前实例化初始化器和监听器的一样,一样调用的是 getSpringFactoriesInstances 方法来获取配置的异常类名称并实例化所有的异常处理类。
该异常报告处理类配置在 spring-boot-2.1.4.RELEASE.jar!/META-INF/spring.factories 这个配置文件里面。

# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers

第10步
准备应用上下文

prepareContext(context, environment, listeners, applicationArguments,
					printedBanner);
					
private void prepareContext(ConfigurableApplicationContext context,
			ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments, Banner printedBanner) {
		//绑定环境到上下文
		context.setEnvironment(environment);
		//配置上下文的 bean 生成器及资源加载器
		postProcessApplicationContext(context);
		//为上下文应用所有初始化器
		applyInitializers(context);
		//触发所有 SpringApplicationRunListener 监听器的 contextPrepared 事件方法
		listeners.contextPrepared(context);
		//记录启动日志
		if (this.logStartupInfo) {
			logStartupInfo(context.getParent() == null);
			logStartupProfileInfo(context);
		}
		//注册两个特殊的单例bean
		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
		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");
		load(context, sources.toArray(new Object[0]));
		//触发所有 SpringApplicationRunListener 监听器的 contextLoaded 事件方法
		listeners.contextLoaded(context);
	}

第11步
刷新应用上下文

refreshContext(context);

private void refreshContext(ConfigurableApplicationContext context) {
    refresh(context);
    if (this.registerShutdownHook) {
        try {
            context.registerShutdownHook();
        }
        catch (AccessControlException ex) {
            // Not allowed in some environments.
        }
    }
}

第12步
应用上下文刷新后置处理

afterRefresh(context, applicationArguments);

//默认方法体是空,可以做一些自定义的后置处理操作。
protected void afterRefresh(ConfigurableApplicationContext context,
		ApplicationArguments args) {
}

第13步
停止计时监控类
计时监听器停止,并统计一些任务执行信息。

stopWatch.stop();
public void stop() throws IllegalStateException {
		if (this.currentTaskName == null) {
			throw new IllegalStateException("Can't stop StopWatch: it's not running");
		}
		long lastTime = System.currentTimeMillis() - this.startTimeMillis;
		this.totalTimeMillis += lastTime;
		this.lastTaskInfo = new TaskInfo(this.currentTaskName, lastTime);
		if (this.keepTaskList) {
			this.taskList.add(this.lastTaskInfo);
		}
		++this.taskCount;
		this.currentTaskName = null;
	}

第14步
输出日志记录执行主类名、时间信息

if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass)
						.logStarted(getApplicationLog(), stopWatch);
			}

第15步
发布应用上下文启动完成事件

listeners.started(context);

第16步
执行所有 Runner 运行器

callRunners(context, applicationArguments);

//callRunners实现
private void callRunners(ApplicationContext context, ApplicationArguments args) {
		List<Object> runners = new ArrayList<>();
		runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
		runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
		AnnotationAwareOrderComparator.sort(runners);
		for (Object runner : new LinkedHashSet<>(runners)) {
			if (runner instanceof ApplicationRunner) {
				callRunner((ApplicationRunner) runner, args);
			}
			if (runner instanceof CommandLineRunner) {
				callRunner((CommandLineRunner) runner, args);
			}
		}
	}

第17步
发布应用上下文就绪事件
触发所有 SpringApplicationRunListener 监听器的 running 事件方法。

listeners.running(context);

第18步
返回应用上下文

return context;

请关注我的订阅号

订阅号.png

> # 查看每列的数据类型 > str(df) tibble [12,299 × 37] (S3: tbl_df/tbl/data.frame) $ ...1 : num [1:12299] 0 1 2 3 4 5 6 7 8 9 ... $ STATION : chr [1:12299] "00841599999" "01001099999" "01001499999" "01002099999" ... $ NAME : chr [1:12299] "XM21" "JAN MAYEN NOR NAVY, NO" "SORSTOKKEN, NO" "VERLEGENHUKEN, NO" ... $ LATITUDE : num [1:12299] NA 70.9 59.8 80 77 ... $ LONGITUDE : num [1:12299] NA -8.67 5.34 16.25 15.5 ... $ ELEVATION : num [1:12299] NA 9 48.8 8 12 ... $ 2020-01-01: num [1:12299] NA 18.3 17 19.6 25.6 16.1 18.7 17 14.8 28.9 ... $ 2020-01-02: num [1:12299] NA 42.6 33.7 22.6 36.5 9.4 22.6 20.9 11.7 36.7 ... $ 2020-01-03: num [1:12299] NA 61.9 28.7 31.1 33.3 22.8 22.4 26.3 23.3 23.7 ... $ 2020-01-04: num [1:12299] NA 24.6 NA 33.7 8.9 38.2 16.7 21.1 26.7 42.2 ... $ 2020-01-05: num [1:12299] NA 23.9 19.3 18.5 12.2 17.6 5.2 12.4 21.9 29.4 ... $ 2020-01-06: num [1:12299] NA 24.8 26.7 31.5 47.2 7.6 10.4 21.1 9.8 23.3 ... $ 2020-01-07: num [1:12299] NA 29.3 30 62.4 55.6 27.6 64.8 50.6 34.4 28.2 ... $ 2020-01-08: num [1:12299] NA 40.7 28 68.3 39.8 52.2 40.4 44.1 59.8 34.4 ... $ 2020-01-09: num [1:12299] NA 24.1 11.3 56.5 48.2 44.6 41.1 51.5 46.5 57.2 ... $ 2020-01-10: num [1:12299] NA 18.5 9.4 33.9 44.6 16.1 22 36.7 20.2 26.1 ... $ 2020-01-11: num [1:12299] NA 52.6 NA 17.4 39.6 4.1 9.1 22.4 8.1 29.3 ... $ 2020-01-12: num [1:12299] NA 53.7 15.4 16.3 40.7 3.5 11.5 21.7 5.7 29.4 ... $ 2020-01-13: num [1:12299] NA 57.2 22.2 25.4 18.7 9.6 11.1 13.3 12 29.8 ... $ 2020-01-14: num [1:12299] NA 52 24.3 34.3 9.6 19.8 5.4 12.4 34.4 22 ... $ 2020-01-15: num [1:12299] NA 61.1 24.8 26.1 10.6 14.8 5.4 6.9 25.4 10.4 ... $ 2020-01-16: num [1:12299] NA 38.7 21.1 38.7 7 25.9 6.5 9.4 38.2 14.6 ... $ 2020-01-17: num [1:12299] NA 42.2 19.4 18.5 12.8 17.6 6.3 8.7 NA 33.2 ... $ 2020-01-18: num [1:12299] NA 30.4 NA 32 10.2 31.3 9.4 16.3 NA 22.4 ... $ 2020-01-19: num [1:12299] NA 32.8 14.3 37.8 23 26.3 24.1 25.6 NA 23.3 ... $ 2020-01-20: num [1:12299] NA 40.2 15 25.7 27.2 23.9 9.8 22.4 27.8 38 ... $ 2020-01-21: num [1:12299] NA 32.6 12.6 33 32.6 25.7 18 27.2 21.7 35.7 ... $ 2020-01-22: num [1:12299] NA 27.8 6.7 19.3 14.1 26.7 6.1 14.6 NA 27.2 ... $ 2020-01-23: num [1:12299] NA 33.2 16.1 52.4 51.1 8.7 36.3 42.4 NA 32 ... $ 2020-01-24: num [1:12299] NA 29.1 11.9 37.6 59.3 19.8 33 50.6 NA 18.5 ... $ 2020-01-25: num [1:12299] NA 22.6 NA 21.3 32.6 10 8.5 21.1 NA 33 ... $ 2020-01-26: num [1:12299] NA 56.9 24.8 16.9 10.2 10.7 8.1 8.5 NA 38.3 ... $ 2020-01-27: num [1:12299] NA 38.7 14.3 35.2 9.3 23.3 8.1 4.8 NA 35 ... $ 2020-01-28: num [1:12299] NA 20.7 9.1 31.3 21.9 20.9 15.7 4.1 NA 34.8 ... $ 2020-01-29: num [1:12299] NA 10.7 10 20.6 30.2 25.4 38 25 35.9 34.3 ... $ 2020-01-30: num [1:12299] NA 21.1 19.1 19.6 25.6 27.8 26.9 21.1 NA 33.3 ... $ 2020-01-31: num [1:12299] NA 35.7 19.1 19.6 14.1 21.7 10.7 4.4 NA 36.9 ...,现在的列名是这样,根据这个修改代码
最新发布
10-29
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码哥说

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值