接上一篇SpringBoot启动时都做了哪些事(一)后,我们继续分析SpringBoot启动过程流程。
核心流程梳理:
- 实例化监控StopWatch用于记录起止时间
- 设置java.awt.headless属性,默认为true。Headless模式是系统的一种配置模式。在系统可能缺少显示设备、键盘或鼠标这些外设的情况下可以使用该模式。
- 获取SpringApplicationRunListener,触发
listeners.starting();
广播ApplicationStartingEvent事件 - 获取DefaultApplicationArguments
创建并配置环境,广播ApplicationEnvironmentPreparedEvent
创建environment,实例化过程中会放入四个propertySource:servletContextInitParams、servletConfigInitParams、systemEnvironment、systemProperties
configureEnvironment
获取到一个ConversionService 赋予environment
尝试添加defaultProperties、springApplicationCommandLineArgs对应的propertySource,本文这里为空不添加
将SpringApplication实例的Set additionalProfiles集合与spring.profiles.active信息合并并赋予environment的Set activeProfiles集合
添加configurationProperties对应的ConfigurationPropertySourcesPropertySource
listeners.environmentPrepared(environment);触发每个监听器的环境准备方法,本文这里是广播ApplicationEnvironmentPreparedEvent事件,增加三个PropertySource包括OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.properties]'}
将环境与SpringApplication绑定起来
设置spring.beaninfo.ignore属性,默认为true
打印banner
创建应用上下文实例AnnotationConfigServletWebServerApplicationContext,这里会同步创建AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner并注册一系列"AnnotationConfigProcessor"。其父类GenericApplicationContext的构造方法中会触发DefaultListableBeanFactory的实例化流程。
获取SpringBootExceptionReporter
prepareContext
为应用上下文绑定环境实例
尝试为context设置ResourceLoader和ClassLoader,并为BeanFactory进行设置ConversionService
触发每一个ApplicationContextInitializer的initialize方法
listeners.contextPrepared(context);广播ApplicationContextInitializedEvent事件
注册单例springApplicationArguments—DefaultApplicationArguments
注册单例springBootBanner
设置是否允许BeanDefinition覆盖,默认是false
加载主启动应用类,注册启动类的BeanDefinition到容器DefaultListableBeanFactory
listeners.contextLoaded(context);触发EventPublishingRunListener的contextLoaded方法,广播ApplicationPreparedEvent事件refreshContext并注册销毁钩子
afterRefresh默认是空方法
listeners.started(context);触发ApplicationStartedEvent事件广播
触发ApplicationRunner和CommandLineRunner run方法执行
listeners.running(context);广播ApplicationReadyEvent事件
SpringApplication的run方法
public ConfigurableApplicationContext run(String... args) { //实例化监控 StopWatch stopWatch = new StopWatch(); // 设置开始时间startTimeNanos = System.nanoTime(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); // 设置java.awt.headless属性,默认为true configureHeadlessProperty(); // 本质是获取SpringApplicationRunListener,触发其starting方法 SpringApplicationRunListeners listeners = getRunListeners(args); //广播ApplicationStartingEvent事件 listeners.starting(); try { //应用的外部参数,本文这里没有指定 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 这块处理environment-创建并配置环境,广播ApplicationEnvironmentPreparedEvent ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); // 设置spring.beaninfo.ignore属性,默认为true configureIgnoreBeanInfo(environment); //打印banner Banner printedBanner = printBanner(environment); // 这块开始创建并处理ApplicationContext context = createApplicationContext(); // 获取SpringBootExceptionReporter 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); } // 触发ApplicationStartedEvent事件广播 listeners.started(context); //触发ApplicationRunner和CommandLineRunner run方法执行 callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { // 广播ApplicationReadyEvent事件 listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context; }
StopWatch
StopWatch 是什么呢?其是位于 org.springframework.util 包下的一个工具类,通过它可方便的对程序部分代码进行计时(ms级别),适用于同步单线程代码块。
简单总结一句,Spring提供的计时器StopWatch对于秒、毫秒为单位方便计时的程序,尤其是单线程、顺序执行程序的时间特性的统计输出支持比较好。
也就是说假如我们手里面有几个在顺序上前后执行的几个任务,而且我们比较关心几个任务分别执行的时间占用状况,希望能够形成一个不太复杂的日志输出,StopWatch提供了这样的功能。而且Spring的StopWatch基本上也就是仅仅为了这样的功能而实现。
注意事项:
StopWatch对象不是设计为线程安全的,并且不适用同步。
一个StopWatch实例一次只能开启一个task,不能同时start多个task
在该task还没stop之前不能start一个新的task,必须在该task stop之后才能开启新的task
若要一次开启多个,需要new不同的StopWatch实例
【1】 运行监听器的获取与启动
① 获取SpringApplicationRunListeners
获取SpringApplicationRunListener
,这里获取的是EventPublishingRunListener
,然后实例化得到一个SpringApplicationRunListeners
对象。
private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; // getSpringFactoriesInstances不再赘述 return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)); }
如下所示,EventPublishingRunListener
实例内部维护了SimpleApplicationEventMulticaster
,通过该实例将事件进行了广播。
public EventPublishingRunListener(SpringApplication application, String[] args) { this.application = application; this.args = args; this.initialMulticaster = new SimpleApplicationEventMulticaster(); for (ApplicationListener<?> listener : application.getListeners()) { this.initialMulticaster.addApplicationListener(listener); } }
② 启动SpringApplicationRunListener
这里会触发SpringApplicationRunListeners
的starting方法,如下所示遍历内部维护的listeners挨个触发其starting方法。
void starting() { for (SpringApplicationRunListener listener : this.listeners) { listener.starting(); } }
以EventPublishingRunListener为例,其会广播ApplicationStartingEvent事件。除了ApplicationStartingEvent外还有ApplicationPreparedEvent、ApplicationReadyEvent、ApplicationFailedEvent事件。
@Override public void starting() { this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args)); }
这里有如下所示五个监听器对ApplicationStartingEvent事件感兴趣。
0 = {RestartApplicationListener@1869} 1 = {LoggingApplicationListener@1873} 2 = {BackgroundPreinitializer@1874} 3 = {DelegatingApplicationListener@1876} 4 = {LiquibaseServiceLocatorApplicationListener@1881}
【2】ConfigurableEnvironment的处理
这部分我们开始分析environment 的实例化与配置。
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
SpringApplication的prepareEnvironment方法。
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // Create and configure the environment ConfigurableEnvironment environment = getOrCreateEnvironment(); //配置环境 configureEnvironment(environment, applicationArguments.getSourceArgs()); //添加configurationProperties对应的ConfigurationPropertySourcesPropertySource ConfigurationPropertySources.attach(environment); // 触发每个监听器的环境准备方法,本文这里是广播ApplicationEnvironmentPreparedEvent事件 // 增加三个PropertySource: //RandomValuePropertySource {name='random'} //OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.properties]'} //MapPropertySource {name='devtools'} listeners.environmentPrepared(environment); //将环境与SpringApplication绑定起来 bindToSpringApplication(environment); if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } // 再次调用,更新configurationProperties对应的source ConfigurationPropertySources.attach(environment); return environment; }
① getOrCreateEnvironment
getOrCreateEnvironment
会根据webApplicationType实例化一个对象,本文这里是
tandardServletEnvironment
实例。
StandardServletEnvironment继承StandardEnvironment继承自AbstractEnvironment。
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(); } }
在其实例化过程中会触发父类AbstractEnvironment的customizePropertySources
方法。
这里会放入servletContextInitParams、servletConfigInitParams对应的propertySource。
protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME)); propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME)); if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) { propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME)); } super.customizePropertySources(propertySources); }
StandardEnvironment的customizePropertySources方法会放入systemEnvironment、systemProperties对应的propertySource。
protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast( new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); propertySources.addLast( new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment())); }
也就是说实例化Environment后,其内部已经有了四个propertySource:servletContextInitParams、servletConfigInitParams、systemEnvironment、systemProperties。
② configureEnvironment
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { // 默认为true if (this.addConversionService) { // 获取到一个ConversionService 赋予environment ConversionService conversionService = ApplicationConversionService.getSharedInstance(); environment.setConversionService((ConfigurableConversionService) conversionService); } // 尝试添加defaultProperties、springApplicationCommandLineArgs对应的propertySource,本文这里为空不添加 configurePropertySources(environment, args); configureProfiles(environment, args); }
方法逻辑梳理如下:
- 获取到一个
ConversionService
赋予environment - 尝试添加
defaultProperties
、springApplicationCommandLineArgs
对应的propertySource
,本文这里为空不添加 - 将SpringApplication实例的
Set additionalProfiles
集合与spring.profiles.active信息
合并并赋予environment的Set activeProfiles集合
① getSharedInstance
这里getSharedInstance比较有意思,其获取的单例方式是典型的双重校验锁并使用了volatile 关键字保证内存可见性。
// ApplicationConversionService extends FormattingConversionService private static volatile ApplicationConversionService sharedInstance; public static ConversionService getSharedInstance() { ApplicationConversionService sharedInstance = ApplicationConversionService.sharedInstance; if (sharedInstance == null) { synchronized (ApplicationConversionService.class) { sharedInstance = ApplicationConversionService.sharedInstance; if (sharedInstance == null) { sharedInstance = new ApplicationConversionService(); ApplicationConversionService.sharedInstance = sharedInstance; } } } return sharedInstance; }
ApplicationConversionService实例化过程会进行一些默认配置,添加常见的、基础的转换器和格式化器。
public static void configure(FormatterRegistry registry) { DefaultConversionService.addDefaultConverters(registry); DefaultFormattingConversionService.addDefaultFormatters(registry); addApplicationFormatters(registry); addApplicationConverters(registry); }
② configurePropertySources
如下所示获取到environment实例化后的sources ,然后尝试添加defaultProperties、springApplicationCommandLineArgs对应的source到environment中。
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) { // 默认此时只有四个-在初始化时候添加的servletConfigInitParams servletContextInitParams // systemProperties systemEnvironment MutablePropertySources sources = environment.getPropertySources(); //本文这里SpringApplication.defaultProperties == null if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) { sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties)); } // 尝试将命令行参数包装为source放到MutablePropertySources sources中 if (this.addCommandLineProperties && args.length > 0) { //commandLineArgs String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME; if (sources.contains(name)) { PropertySource<?> source = sources.get(name); CompositePropertySource composite = new CompositePropertySource(name); composite.addPropertySource( new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args)); composite.addPropertySource(source); sources.replace(name, composite); } else { sources.addFirst(new SimpleCommandLinePropertySource(args)); } } }
③ configureProfiles
获取SpringApplication的Set additionalProfiles与environment里面的spring.profiles.active进行合并然后赋予environment对象的成员Set activeProfiles 。本文这里都是空的数据对象。
// SpringApplication protected void configureProfiles(ConfigurableEnvironment environment, String[] args) { Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles); profiles.addAll(Arrays.asList(environment.getActiveProfiles())); environment.setActiveProfiles(StringUtils.toStringArray(profiles)); }
additionalProfiles里面存放的是什么呢?要使用的其他配置文件值(在系统或命令行属性中设置的配置文件值之上),就是激活的配置文件路径(包含文件名称)。
③ ConfigurationPropertySources.attach
检测是否存在configurationProperties对应的PropertySource,初始化ConfigurationPropertySourcesPropertySource并放到sources 的首位。
// ConfigurationPropertySources public static void attach(Environment environment) { Assert.isInstanceOf(ConfigurableEnvironment.class, environment); MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources(); // configurationProperties PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME); // 判断所属source是否一致,不一致则从sources移除 if (attached != null && attached.getSource() != sources) { sources.remove(ATTACHED_PROPERTY_SOURCE_NAME); attached = null; } // 放在首位 ConfigurationPropertySourcesPropertySource if (attached == null) { sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME, new SpringConfigurationPropertySources(sources))); } }
方法执行后得到的sources如下所示:
需要注意的是在prepareEnvironment方法中这个方法触发了两次,最后一次会进入if (attached != null && attached.getSource() != sources)
判断体中。
④ environmentPrepared
如下所示,遍历监听器触发environmentPrepared
方法。以EventPublishingRunListener
为例最终是广播ApplicationEnvironmentPreparedEvent
事件。
void environmentPrepared(ConfigurableEnvironment environment) { for (SpringApplicationRunListener listener : this.listeners) { listener.environmentPrepared(environment); } }
如下是对ApplicationEnvironmentPreparedEvent事件感兴趣的监听器。
0 = {RestartApplicationListener@1984} 1 = {ConfigFileApplicationListener@3583} 2 = {AnsiOutputApplicationListener@3584} 3 = {LoggingApplicationListener@1985} 4 = {BackgroundPreinitializer@1986} 5 = {ClasspathLoggingApplicationListener@3585} 6 = {DelegatingApplicationListener@1987} 7 = {FileEncodingApplicationListener@3586}
经过事件广播后,新增了三个PropertySource:
5 = {RandomValuePropertySource@3049} "RandomValuePropertySource {name='random'}" 6 = {OriginTrackedMapPropertySource@3050} "OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.properties]'}" 7 = {MapPropertySource@3051} "MapPropertySource {name='devtools'}"
【3】configureIgnoreBeanInfo
SpringApplication
的configureIgnoreBeanInfo
方法,为System设置属性 spring.beaninfo.ignore= true
。
private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) { // spring.beaninfo.ignore if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) { Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE); // 为System设置属性 spring.beaninfo.ignore= true System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString()); } }
关于banner打印这里不展开分析,有兴趣的可以参考博文:SpringBoot中是如何打印banner的呢?