接着上篇继续分析 SpringBoot 的启动过程。java
SpringBoot的版本为:2.1.0 release,最新版本。spring
同样的,咱们先把时序图贴上来,方便理解:数组
回顾一下,前面咱们分析到了下面这步:bash
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { // 前面分析了前一部分,SpringApplication的构造方法,本文分析后一部分的run()方法 return new SpringApplication(primarySources).run(args); }
SpringApplication#run方法的内容较多,准备刷屏了:服务器
public ConfigurableApplicationContext run(String... args) { // StopWatch是Spring中一个任务执行时间控制的类,记录了任务的执行时间 StopWatch stopWatch = new StopWatch(); stopWatch.start();// 开始时间 ConfigurableApplicationContext context = null; // 建立一个SpringBootExceptionReporter的List,记录启动过程当中的异常信息 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); // 设置系统属性 "java.awt.headless" 为 true configureHeadlessProperty(); // 获取全部启动监听器 SpringApplicationRunListeners listeners = getRunListeners(args); // 调用全部监听器的starting() listeners.starting(); try { // 根据入参建立 ApplicationArguments 对象 ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); // 准备应用上下文 environment 对象 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); // 设置系统属性 "spring.beaninfo.ignore",是否忽略bean信息 configureIgnoreBeanInfo(environment); // 获取打印 Banner 对象,也就是应用启动时候看到控制台输出的那个很大的 "SpringBoot" Banner printedBanner = printBanner(environment); // 建立IOC容器 context = createApplicationContext(); // 获取异常报告,在异常时报告异常信息 exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); // 准备上下文 prepareContext(context, environment, listeners, applicationArguments, printedBanner); // 刷新上下文 refreshContext(context); // 刷新完后 afterRefresh(context, applicationArguments); stopWatch.stop();// 启动完成,记录启动结束时间 if (this.logStartupInfo) {// 打印详细启动时间信息 new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } // 调用全部监听器的started(ctx) listeners.started(context); // 调用实现了 ApplicationRunner、CommandLineRunner 的对象的 run 方法 callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context);// 调用全部监听器的running() } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context;// 返回上下文 }
上面从大致上介绍了SpringApplication在run()中作的事情,下面详细分析每步具体的操做。app
SpringApplication#getRunListeners,获取全部启动监听器,同样的套路,经过SPI机制,经过工厂建立SpringApplicationRunListener的实例:less
private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; // 注意这个地方,咱们第二次看到了getSpringFactoriesInstances这个方法,做用就是从类路径下 META-INF/spring.factories 下加载 SpringFactory 实例,最后传入 SpringApplicationRunListener.class,建立实例 return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances( SpringApplicationRunListener.class, types, this, args)); }
从 spring.factories 里面找到以下内容:也就是最后拿到SpringApplicationRunListener的实例是EventPublishingRunListener的对象。源码分析
# Run Listeners org.springframework.boot.SpringApplicationRunListener=\ org.springframework.boot.context.event.EventPublishingRunListener
SpringApplication#configureHeadlessProperty,设置系统属性 "java.awt.headless" 。headless是系统的一种配置模式,在系统可能缺乏显示设备、键盘或鼠标这些外设的状况下可使用该模式,也就是告诉服务器,没有这些硬件设施,当须要这是设备信息的时候,别慌,我可使用awt组件经过计算模拟出这些外设的特性。post
private void configureHeadlessProperty() { // this.headless在初始化的时候值为 "true" System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty( SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless))); }
SpringApplication#prepareEnvironment,准备应用上下文 environment 对象,像设置当前使用的配置文件profile,就在该方法内完成:网站
private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); // 添加 ConversionService 转换器,保存启动参数 configureEnvironment(environment, applicationArguments.getSourceArgs()); // 调用全部监听器的environmentPrepared(env) listeners.environmentPrepared(environment); // 将 environment 绑定到当前Spring上下文 bindToSpringApplication(environment); // 判断是否用户自定义Environment if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()) .convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } ConfigurationPropertySources.attach(environment); return environment; }
SpringApplication#configureEnvironment,添加 ConversionService 转换器,保存启动参数,即最外面main的入参args数组:
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { if (this.addConversionService) { ConversionService conversionService = ApplicationConversionService .getSharedInstance(); environment.setConversionService( (ConfigurableConversionService) conversionService); } // 合并命令行启动参数到当前 enviroment,这个args就是最外层App启动传入的main方法的参数 configurePropertySources(environment, args); // 设置当前运行环境的配置文件(dev/uat/prod) configureProfiles(environment, args); }
SpringApplication#configureProfiles ,设置当前运行环境的配置文件,会去查看"spring.profiles.active"中指定的是什么:
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) { // 去找当前激活的Profile是哪一个 "spring.profiles.active" environment.getActiveProfiles(); // ensure they are initialized // But these ones should go first (last wins in a property key clash) Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles); profiles.addAll(Arrays.asList(environment.getActiveProfiles())); environment.setActiveProfiles(StringUtils.toStringArray(profiles)); }
不当心有陷入进去了,再次回到run()。
接着看后面是获取Banner。根据环境找到Banner,默认找classpath下面的 banner.txt,Gitee上面不少管理系统打印出各类自定义名称。实现很简单,只要你提早制做好banner.txt,放到resources下面,启动的时候,就会加载你的,覆盖原始的Banner信息。这部分代码比较简单,只要跟进去看下就能看明白,考虑篇幅问题,省略过了。分享一个在线设计Banner.txt的网站。
若是须要关闭Banner输出,在App里面调用:
SpringApplication.setBannerMode(Banner.Mode.OFF);// 关掉Banner
接下来重点看一下 SpringApplication#prepareContext,分析SpringBoot如何准备上下文的:
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment, SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) { // 给当前 context 设置 environment context.setEnvironment(environment); // 设置 beanNameGenerator、resourceLoader、ConversionService postProcessApplicationContext(context); // 获取全部 ApplicationContextInitializer,调用其 initialize(ctx) 方法 applyInitializers(context); // 调用全部监听器的contextPrepared(ctx) listeners.contextPrepared(context); if (this.logStartupInfo) { // 打印启动进程信息 logStartupInfo(context.getParent() == null); logStartupProfileInfo(context);// 打印 profile 信息 } // Add boot specific singleton beans 注册 SpringBoot 特有的单实例 ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); beanFactory.registerSingleton("springApplicationArguments", applicationArguments); if (printedBanner != null) { beanFactory.registerSingleton("springBootBanner", printedBanner); } if (beanFactory instanceof DefaultListableBeanFactory) { // 设置是否容许重写 BeanDefinition ((DefaultListableBeanFactory) beanFactory) .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding); } // Load the sources Set<Object> sources = getAllSources(); Assert.notEmpty(sources, "Sources must not be empty"); // 建立 BeanDefinitionLoader,用于加载 BeanDefinition,此处加载应用启动类有@SpringBootApplication 注解的主类 load(context, sources.toArray(new Object[0])); // 调用全部监听器的contextLoaded(ctx) listeners.contextLoaded(context); }
稳住,快启动完了。接着看SpringApplication#refreshContext,完成刷新上下文的操做:
private void refreshContext(ConfigurableApplicationContext context) { refresh(context);// 刷新上下文,最后使用了AbstractApplicationContext#refresh方法,进入了Spring的启动流程 if (this.registerShutdownHook) { try { // 注册中止钩子,为了在应用程序shutdown的时候销毁全部 bean 实例 context.registerShutdownHook(); } catch (AccessControlException ex) { // Not allowed in some environments. } } }
而后是到了SpringApplication#afterRefresh,这是一个模板方法,父类不提供实现,留给子类发挥想象实现,在context刷新好以后须要作的事情能够在此方法实现中完成。
最后就是 stopWatch.stop() 中止计时,打印启动耗时信息,回调监听器的started(ctx)方法,返回上下文context。
至此,SpringBoot启动过程分析完成。