Springboot:SpringApplication启动流程

Springboot:SpringApplication启动流程

  • SpringBoot项目启动步骤如下所示
    • 1.实例化SpringApplication对象
    • 2.开始执行run()方法
    • 3.配置headless属性
    • 4.获取SpringApplicationRunListeners
    • 5.回调SpringApplicationRunListener对象的starting()方法
    • 6.解析run()方法的args参数并封装为DefaultApplicationArguments类
    • 7.准备一个Environment对象
    • 8.设置系统参数
    • 9.获取需要打印的SpringBoot启动Banner对象
    • 10.创建Spring容器ApplicationContext
    • 11.准备ApplicationContext实例
    • 12.刷新容器
    • 13.调用afterRefresh()方法
    • 14.代码执行时间的监控停止
    • 15.发布容器启动事件
    • 16.遍历执行CommandLineRunner

Spring Boot项目通过运行启动类中的run()方法就可以将整个应用启动。

public class SpringApplication {public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {return run(new Class[]{primarySource}, args);}public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {//1.实例化SpringApplication,之后执行run()方法return (new SpringApplication(primarySources)).run(args);}//最终执行的run()方法
public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();//2.代码执行时间监控开启stopWatch.start();ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();//3.配置headless属性,默认为truethis.configureHeadlessProperty();//4.获取SpringApplicationRunListener集合SpringApplicationRunListeners listeners = this.getRunListeners(args);//5.回调所有SpringApplicationRunListener对象的starting()方法listeners.starting();Collection exceptionReporters;try {//6.创建ApplicationArguments对象ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);//7.创建Environment对象,加载属性配置ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);//8.设置系统参数this.configureIgnoreBeanInfo(environment);//9.获取打印的Spring Boot bannerBanner printedBanner = this.printBanner(environment);//10.创建Spring容器ApplicationContextcontext = this.createApplicationContext();exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);//11.准备容器this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);//12.刷新Spring容器this.refreshContext(context);//13.执行Spring容器初始化的后置逻辑this.afterRefresh(context, applicationArguments);//14.代码执行时间监控结束stopWatch.stop();//打印Spring Boot的启动时长日志if (this.logStartupInfo) {(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);}//15.发布容器启动事件listeners.started(context);//16.调用ApplicationRunner或者CommandLineRunner的运行方法this.callRunners(context, applicationArguments);} catch (Throwable var10) {this.handleRunFailure(context, var10, exceptionReporters, listeners);throw new IllegalStateException(var10);}try {listeners.running(context);return context;} catch (Throwable var9) {this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);throw new IllegalStateException(var9);}}

SpringBoot项目启动步骤如下所示

1.实例化SpringApplication对象

在执行run()方法前,使用new SpringApplication()构造SpringApplication对象。

SpringApplication类的构造方法如下所示:

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {this.sources = new LinkedHashSet();this.bannerMode = Mode.CONSOLE;this.logStartupInfo = true;this.addCommandLineProperties = true;this.addConversionService = true;this.headless = true;this.registerShutdownHook = true;this.additionalProfiles = new HashSet();this.isCustomEnvironment = false;this.resourceLoader = resourceLoader;Assert.notNull(primarySources, "PrimarySources must not be null");this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));this.webApplicationType = WebApplicationType.deduceFromClasspath();this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));this.mainApplicationClass = this.deduceMainApplicationClass();}

这一步主要是构造SpringApplication对象,并为SpringApplication的属性赋值,在构造完成后,开始执行run()方法。

webApplicationType值的设置,其目的是获取当前应用的类型,对后续步骤构造容器环境和Spring容器的初始化起到作用。
该值得获取是通过调用WebApplicationType.deduceFromClasspath()方法得到的。

public enum WebApplicationType {//当前应用不是Web应用,不需要启动一个内嵌的Web服务器NONE,//当前应用需要启动一个内嵌的基于Servlet的服务器SERVLET,//当前应用需要启动一个内嵌的基于Reactive的服务器REACTIVE;private static final String[] SERVLET_INDICATOR_CLASSES = new String[]{"javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext"};private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";private WebApplicationType() {}static WebApplicationType deduceFromClasspath() {if (ClassUtils.isPresent("org.springframework.web.reactive.DispatcherHandler", (ClassLoader)null) && !ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", (ClassLoader)null) && !ClassUtils.isPresent("org.glassfish.jersey.servlet.ServletContainer", (ClassLoader)null)) {return REACTIVE;} else {String[] var0 = SERVLET_INDICATOR_CLASSES;int var1 = var0.length;for(int var2 = 0; var2 < var1; ++var2) {String className = var0[var2];if (!ClassUtils.isPresent(className, (ClassLoader)null)) {return NONE;}}return SERVLET;}}static WebApplicationType deduceFromApplicationContext(Class<?> applicationContextClass) {if (isAssignable("org.springframework.web.context.WebApplicationContext", applicationContextClass)) {return SERVLET;} else {return isAssignable("org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext", applicationContextClass) ? REACTIVE : NONE;}}private static boolean isAssignable(String target, Class<?> type) {try {return ClassUtils.resolveClassName(target, (ClassLoader)null).isAssignableFrom(type);} catch (Throwable var3) {return false;}}
}

2.开始执行run()方法

代码执行时间的监控开启,在SpringBoot应用启动后会打印启动时间。

3.配置headless属性

该代码的作用是SpringBoot应用在启动时,没有检测到显示器也能够继续执行后面的步骤。

4.获取SpringApplicationRunListeners

getRunListeners()方法的源码如下所示:

    private SpringApplicationRunListeners getRunListeners(String[] args) {Class<?>[] types = new Class[]{SpringApplication.class, String[].class};return new SpringApplicationRunListeners(logger, this.getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));}private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {return this.getSpringFactoriesInstances(type, new Class[0]);}private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {ClassLoader classLoader = this.getClassLoader();Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);AnnotationAwareOrderComparator.sort(instances);return instances;}

5.回调SpringApplicationRunListener对象的starting()方法

6.解析run()方法的args参数并封装为DefaultApplicationArguments类

7.准备一个Environment对象

prepareEnvironment()方法的作用就是为当前应用准备一个Environment对象,主要完成对ConfigurableEnvironment的初始化工作。

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {//创建环境对象ConfigurableEnvironment environment = this.getOrCreateEnvironment();// 配置环境this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());//触发监听器listeners.environmentPrepared((ConfigurableEnvironment)environment);//为SpringApplication 绑定 environmentthis.bindToSpringApplication((ConfigurableEnvironment)environment);if (!this.isCustomEnvironment) {environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass());}//为environment其他附件配置信息ConfigurationPropertySources.attach((Environment)environment);return (ConfigurableEnvironment)environment;}

在创建环境完成后,接下来是配置环境,configureEnvironment()方法源码如下所示:

    protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {if (this.addConversionService) {ConversionService conversionService = ApplicationConversionService.getSharedInstance();environment.setConversionService((ConfigurableConversionService)conversionService);}//加载启动命令行配置属性this.configurePropertySources(environment, args);//设置active属性this.configureProfiles(environment, args);}

该方法主要加载一些默认配置,在执行完这一步骤后,会触发监听器(主要触发ConfigFileApplicationListener),将会加载application.properties或者application.yml配置文件

8.设置系统参数

该方法会获取spring.beaninfo.ignore配置项的值,即使未获取也没有关系。代码的最后还是给该配置项输入了一个默认值true。

9.获取需要打印的SpringBoot启动Banner对象

    private Banner printBanner(ConfigurableEnvironment environment) {if (this.bannerMode == Mode.OFF) {return null;} else {ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader : new DefaultResourceLoader(this.getClassLoader());SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter((ResourceLoader)resourceLoader, this.banner);return this.bannerMode == Mode.LOG ? bannerPrinter.print(environment, this.mainApplicationClass, logger) : bannerPrinter.print(environment, this.mainApplicationClass, System.out);}}

10.创建Spring容器ApplicationContext

    protected ConfigurableApplicationContext createApplicationContext() {Class<?> contextClass = this.applicationContextClass;if (contextClass == null) {try {switch(this.webApplicationType) {case SERVLET:contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");break;case REACTIVE:contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");break;default:contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");}} catch (ClassNotFoundException var3) {throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);}}return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);}

通过源码可以看出createApplicationContext()方法的执行逻辑:根据webApplicationType决定创建哪种contextClass。

11.准备ApplicationContext实例

    private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {//将应用环境设置到容器中,包括各种变量context.setEnvironment(environment);//执行容器后置处理this.postProcessApplicationContext(context);//执行容器中的ApplicationContextInitializerthis.applyInitializers(context);//回调SpringApplicationRunListener的contextPrepared()方法listeners.contextPrepared(context);if (this.logStartupInfo) {this.logStartupInfo(context.getParent() == null);this.logStartupProfileInfo(context);}// 添加特定的BeanConfigurableListableBeanFactory 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 = this.getAllSources();Assert.notEmpty(sources, "Sources must not be empty");//加载启动类,将启动类注入容器中this.load(context, sources.toArray(new Object[0]));//回调SpringApplicationRunListener的contextPrepared()方法listeners.contextLoaded(context);}

在创建对应的Spring容器后,程序会进行初始化、加载主启动类等预处理工作,主启动类加载完成,容器准备好。

12.刷新容器

refreshContext()方法源码如下所示:

    private void refreshContext(ConfigurableApplicationContext context) {this.refresh(context);if (this.registerShutdownHook) {try {context.registerShutdownHook();} catch (AccessControlException var3) {}}}

该方法是Spring Bean加载的核心,用于刷新整个Spring上下文信息,定义整个Spring上下文加载的流程。
包括实例的初始化和属性设置、自动配置类的加载和执行、内置Tomcat服务器的启动等步骤。

13.调用afterRefresh()方法

执行Spring容器初始化的后置逻辑,默认实现是一个空的方法

14.代码执行时间的监控停止

知道了启动应用所花费的时间

15.发布容器启动事件

16.遍历执行CommandLineRunner

在ApplicationContext完成启动后,程序会对ApplicationRunner和CommandLineRunner进行回调处理,查找当前ApplicationContext中是否注解有CommandLineRunner,如果有,则遍历执行它们。

private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception, Collection<SpringBootExceptionReporter> exceptionReporters, SpringApplicationRunListeners listeners) {try {try {this.handleExitCode(context, exception);if (listeners != null) {listeners.failed(context, exception);}} finally {this.reportFailure(exceptionReporters, exception);if (context != null) {context.close();}}} catch (Exception var9) {logger.warn("Unable to close ApplicationContext", var9);}ReflectionUtils.rethrowRuntimeException(exception);}


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部