流程
SpringApplication 对象
SpringApplication 对象在构造函数中,作了以下的初始化操作
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 资源加载器
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 将启动主程序类作为 LinkHashSet 的第一个元素,以便后续待用
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 推断当前运行的项目是什么类型:非 Web、基于 Servlet API 的 Web、Reactive 的 Web
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
- getSpringFactoriesInstances:这个方法中,加载 Spring 的工厂实例。
应用程序类型
对当前的应用程序进行推断,有三种类型:
- Reactive 的 Web 程序
- 存在:org.springframework.web.reactive.DispatcherHandler
- 不存在:org.springframework.web.servlet.DispatcherServlet、org.glassfish.jersey.servlet.ServletContainer
- 基于 Servlet 的 Web 程序
- 标准程序,非 Web 程序
- 不存在:javax.servlet.Servlet、org.springframework.web.context.ConfigurableWebApplicationContext
这里不同程序需要满足不同的条件;
Servlet 是最后判断的,所以,在前面两种条件都不满足的情况就是 Servlet;
实例化所有 Spring 初始化器
首先,会尝试的将当前接收的类加载 ClassLoader 作为 key 从 cache 中获取是否已经加载过 SpringFactories 相关内容了,一旦存在数据,则会直接返回。
在上面的条件不满足的时候,接着 SpringBoot 会使用 AppClassLoader 类加载,调用 getResources 方法从类路径下加载 META-INF/spring.factories 文件,包含其它依赖包下的。
在这方法中有两种判断:
- 一、通过类加载器,直接从类路径下搜索
- 二、从用于加载类的搜索路径中查找指定名称的所有资源
不管哪种方法,都会将获取的 URLs 遍历,通过 UrlResource 类解析 file: 协议的路径,获取路径所在的文件。
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
将 resource 转为一个 Properties 对象之后,可以很方便的从其中获取相应的值。例如上面,从 Properties 中将所有的键值对数据遍历之后将其添加到 cache 中,以当前 ClassLoader 为 key。
根据 ApplicationContextInitializer.class
全类名,获取到所有的上下文类。
紧接着将所有的 contet 相关的类,都实例化,并且按照注解标注的优先级排序,之后获取所有的 Spring 工厂的实例就到此为止。
返回的实例化工厂对象会被赋值到 SpringApplication对象的 this.initializers
属性。
实例化监听器
与上面实例化 工厂基本一样的逻辑,区别在于这次是从 cache 缓冲中获取的 ApplicationListener.class
全类名的所有监听器。
再将其排序之后,赋值到 SpringApplicatin 的 this.listeners
属性
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
this.listeners = new ArrayList<>(listeners);
}
设置 Main 主类
通过在堆栈跟踪中,获取名为 main 方法的全类名,然后将其通过反射方式实例化,并且赋值给 SpringApplication 的 this.mainApplicationClass
属性:
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
以上就是实例化 SpringApplication 的初始化操作。
运行 SpringApplication run 方法
开启计时监视
开始就是开启程序停止 watch,作用是对项目的启动时间或者其它计时。
Spring 应用程序运行监听器
这里的 listener 与之前在 SpringApplication 不同,在这里不是运行哪些监听器,而是运行一个事件发布监听器。
是什么呢?
EventPublishingRunListenner 类实现 SpringApplicationRunListener 接口,主要用于 SprintApplication 的 run 方法,每次 SpringBoot 启动的时候都会执行该事件侦听器。
跟 Servlet 的监听器不同,该类的监听器必须通过 META-INF/spring.factories 里面的 org.springframework.boot. SpringApplicationRunListener 作为将,然后实现了 SpringApplicationRunListener 接口的类作为值,可以多个,逗号为界。
具体是什么作用呢?
上面提到一点,每次 SpringBoot 运行都会回调这些侦听器,就是遍历,然后调用 starting 方法。
拿现成的侦听器来说,EventPublishingRunListenner 在 starting 里面调用 SimpleApplicationEventMulticaster 类,这个类间接实现底层 ApplicationEventMulticaster 接口,所起本质上看来,就是 SimpleApplicationEventMulticaster 的代理类,调用其 multicastEvent 方法,实例化 ApplicationStartingEvent 类,作为参数传入。
看其源代码,发现好像对日志方面的处理。
需要注意一点,事件的来源是 SpringApplication,这个实例在后期可能被修改。
看到这里了,发现好多接口,以及类;这些类与接口在后面的扩展中有一个详细的表格。
处理环境参数
将 main 的 args 来自命令行的参数进行包装,转换为一个对象 DefaultApplicationArguments。
调用 SpringApplication StandardEnvironment 方法:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
获取环境
通过 getOrCreateEnvironment 获取环境,有四种类型,在四种选一种,类型实现 ConfigurableWebEnvironment 接口
- 自定义类型,也就是通过侦听器
starting
的方法来修改 SpringApplication 的environment
属性。 SERVLET
REACTIVE
- 标准类型,非 Web 类型
添加服务转换器
通过这方法 configureEnvironment 来配置环境信息,这一步在环境信息里面添加了 ApplicationConversionService 服务转换器。
public static void addDefaultConverters(ConverterRegistry converterRegistry) {
addScalarConverters(converterRegistry);
addCollectionConverters(converterRegistry);
converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
converterRegistry.addConverter(new StringToTimeZoneConverter());
converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());
converterRegistry.addConverter(new ObjectToObjectConverter());
converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry));
converterRegistry.addConverter(new FallbackObjectToStringConverter());
converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService) converterRegistry));
}
public static void addDefaultFormatters(FormatterRegistry formatterRegistry) {
// Default handling of number values
formatterRegistry.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
// Default handling of monetary values
if (jsr354Present) {
formatterRegistry.addFormatter(new CurrencyUnitFormatter());
formatterRegistry.addFormatter(new MonetaryAmountFormatter());
formatterRegistry.addFormatterForFieldAnnotation(new Jsr354NumberFormatAnnotationFormatterFactory());
}
// Default handling of date-time values
// just handling JSR-310 specific date and time types
new DateTimeFormatterRegistrar().registerFormatters(formatterRegistry);
if (jodaTimePresent) {
// handles Joda-specific types as well as Date, Calendar, Long
new JodaTimeFormatterRegistrar().registerFormatters(formatterRegistry);
}
else {
// regular DateFormat-based Date, Calendar, Long converters
new DateFormatterRegistrar().registerFormatters(formatterRegistry);
}
}
通过一步步追踪代码,发现在这个方法中,注册了一些默认的服务转换器,有什么有用呢?
比如说,在 controller 的方法形参里面,明明参数有时候前端请求发送的数据是字符串,但若是用 Integer 接收参数也是可以的。这一点就是 String -》Ingeter 的转换器的作用。
上面两个方法,将一些默认的转换器自动添加到环境中。
- 默认转换服务
- 默认转换器
- 默认格式器
- 应用程序转换服务
- 应用程序格式器
- 应用程序转换器
应用程序格式器
应用程序转换器
默认的一共有 124 个转换服务,图片只是一部分
处理 Profile
同样在这方法中,也处理了 Profile 的环境配置;设置了激活的 Profile 配置文件。
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
}
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
调用事件侦听器的 environmentPrepared 方法
代码太复杂,没继续跟踪,大概就是执行环境装备的处理。
配置忽略Bean信息
打印 Banner
就是 SpringBoot 启动的那个拼出来的图形;
创建 IOC 容器
有三种 ioc 容器类型:
- SERVLET
- org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
- REACTIVE
- org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext
- 标准程序,非 Web 程序
- org.springframework.context.annotation.AnnotationConfigApplicationContext
分别带别不同的 ApplicationContext 类,之后使用 bean 的工具包将其实例化转换为 ConfigurableApplicationContext 类型返回。
异常分析报告
就是这行代码,在出现异常的时候;
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
老方式获取;
准备 IOC 容器
对容器设置一些数据;
context, environment, listeners, applicationArguments, printedBanner
在这个阶段,将之前准备好的环境数据,事件侦听器,命令行参数,Banner 输出,对 ioc 进行绑定阶段。
设置 ioc 环境数据
context.setEnvironment(environment)
后置处理 context ioc 容器相关设置
postProcessApplicationContext(context)
在 ioc 后置处理方法中,会对 ioc 容器进行赋值设置,将内部 bean 名称自动生成器、资源加载器、服务转换。
执行所有初始化器
对之前后期到的上下文初始化器遍历执行,调用其中的:
initializer.initialize(context);
这些初始化器都是单例模式。
回调 SpringApplicationRunListener 侦听器
这是调用侦听器的 contextPrepared 方法,ioc 容器作为参数。
启动日志记录
根据 SpringApplication 的属性 logStartupInfo 来决定是否开启日志记录功能,默认是开启的;
想要修改,只能编写 SpringApplicationRunListener 这里边的监听器来初始化 SpringApplication 的数据;
获取 Bean 工厂
返回 SpringApplicaiton 的 beanFactory 属性,这是默认的工厂类:DefaultListableBeanFactory
注册 DefaultApplicationArguments
将这个 DefaultApplicationArguments 实例作为单例注册到 bean 中,默认的 bean 名称:springApplicationArguments
注册 Banner
将 SpringApplicationBannerPrinter 注册到 bean 里边,单例模式,同时默认名称:springBootBanner
设置 Bean 工厂
设置两个参数:
- 允许 Bean 覆盖
- 这个可能不会生效,满足条件必须是:DefaultListableBeanFactory 的子类或者当前实例;
- 懒惰加载
回调 contextLoaded
表示加载完成,然后调用 SpringApplicationRunListener 侦听器,回调所有的监听器的 contextLoaded 方法;
刷新 IOC 容器
简单说就是对容器进行初始化,这个阶段会加载所有组件,bean,启动 Web 服务器等等;
以下的所有代码,就是这个阶段需要完成大绝大部分流程;
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 准备初始化上下文
prepareRefresh();
// 告诉子类刷新内部bean工厂
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 准备bean工厂,以便在此上下文中使用
prepareBeanFactory(beanFactory);
try {
// 允许在上下文子类中对bean工厂进行后置处理
postProcessBeanFactory(beanFactory);
// 调用上下文中注册为bean的工厂后置处理器
invokeBeanFactoryPostProcessors(beanFactory);
// 注册拦截bean创建的bean处理器。
registerBeanPostProcessors(beanFactory);
// 初始化此上下文的消息源
initMessageSource();
// 为此上下文初始化 EventMulticaster
initApplicationEventMulticaster();
// 初始化特定上下文子类中的其他特殊bean
// 例如:启动 Web 服务器
onRefresh();
// 检查侦听器bean并注册它们
registerListeners();
// 实例化所有剩余的(非lazy-init)单例。
finishBeanFactoryInitialization(beanFactory);
// 最后一步:发布相应的事件。
finishRefresh();
} catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// 销毁已经创建的单例,以避免挂起资源。
destroyBeans();
// 重置 active flag
cancelRefresh(ex);
// 将异常传播给调用者
throw ex;
} finally {
// 重置Spring核心的公共内省缓存,因为我们可能不再需要单例bean的元数据了……
resetCommonCaches();
}
}
}
IOC 刷新后处理
这里是一个空方法,SpringBoot 2.x 将 callRunners 移出来,单独作为一个阶段调用;
停止监视器
到了这一阶段 ioc 容器初始化,加载,启动完成了,所以时间记录器到这里关闭。
启动日志记录器
这个是在 SpringBoot 启动的时候,在控制台输出的日志信息;
监听器启动完成阶段回调
遍历所有 ApplicationListener 监听器,调用每一个监听器的 started 方法,ioc 容器作为参数;
回调运行
从 ioc 容器中,获取以下两种类型 bean:
- ApplicationRunner
- CommandLineRunner
对它们根据注解优先级排序之后,遍历回调 run 方法,applicationArguments 作为参数;
应用程序运行中阶段回调
同样回调所有 ApplicationListener 的监听器的 running 方法;
完成
返回 ioc 容器;
总结
- 在最初的实例化 SpringApplication 的时候就对 SpringBoot 所有初始化器、监听器、程序类型推断等上下文环境的数据进行处理,同时将 META-INF/spring.factories 获取到工厂实例进行变量缓存;
- 想要修改 SpringBoot 对象数据,需要在 META-INF/spring.factories 定义名为:org.springframework.context.ApplicationContextInitializer 作为键的格式,后面的值就是对应的需要初始化的初始化器全类名,在 SpringBoot
prepareContext
阶段会加载所有初始化器; - 想要修改 SpringBoot 对象数据,需要在 META-INF/spring.factories 定义名为:org.springframework.boot.SpringApplicationRunListener 作为键的格式,后面的值就是对应的需要初始化的运行时监听器全类名,SpringApplicationRunListener 的事件来自于 SpringBoot 对象,而且这些是运行时的监听器的,与应用和程序监听器 ApplicationListener 不同。
- 想要修改 SpringBoot 对象数据,需要在 META-INF/spring.factories 定义名为:org.springframework.context.ApplicationListener 作为键的格式,后面的值就是对应的需要初始化的监听器,在 SpringBoot 各个阶段都会调用其实现的阶段方法;
- SpringBoot 启动时候会对启动时间进行记录
- 启动的回调有几个阶段,这几个阶段的,所有监听器在需要的时候,会被回调;
- 启动中:
starting
- 启动完成:
started
- 运行中:
running
- 失败:
failed
- 几个特殊的回调:
environmentPrepared
、contextPrepared
、contextLoaded
,分别是在环境准备阶段,上下文准备阶段、上下文加载阶段
- 启动中:
- 从 META-INF/spring.factoies 加载 SpringApplicationRunListener 类型的监听器
- Web 服务器的启动在 refreshContext 阶段的,这个阶段处理的是一些特殊的 bean
- 在 prepareEnvironment 阶段设置了应用程序的环境数据,回调监听器的方法,绑定环境数据、配置文件 Profile等等一些与环境相关的设置;
- 有四个类关于阶段性的回调:SpringApplicationRunListener、ApplicationContextInitializer、ApplicaitonRunner、CommandLineRunner
- SpringApplicationRunListener:最先执行,分阶段执行,需要有一个有参构造器
- ApplicationContextInitializer:其次执行,initialize,
- ApplicaitonRunner、CommandLineRunner:前者优先级高,后者低;
扩展
事件侦听器
名称 | 类型 | 描述 |
---|---|---|
SpringApplicationRunListeners | 类 | 运行 linstener 实例 |
EventPublishingRunListener | 类 | 使用内部 ApplicationEventMulticaster 来处理在实际刷新上下文之前触发的事件。 |
SpringApplicationRunListener | 接口 | 用于 SpringApplication run 方法的侦听器。 SpringApplicationRunListener 是通过 SpringFactoriesLoader 加载的,应该声明一个公共构造函数,该构造函数接受一个 SpringApplication 实例和一个 String[] 参数。每次运行都将创建一个新的SpringApplicationRunListener 实例。 |
SimpleApplicationEventMulticaster | 类 | 简单的实现 ApplicationEventMulticaster 接口。 将所有事件广播给所有已注册的侦听器,让侦听器忽略它们不感兴趣的事件。 侦听器通常会对传入的事件对象执行相应的 instanceof 检查。 |
AbstractApplicationEventMulticaster | 抽象类 | 抽象实现了 ApplicationEventMulticaster 接口,提供了基本的监听器注册功能。 默认情况下不允许同一个监听器有多个实例,因为它将监听器保持在一个链接集中。用于保存 ApplicationListener 对象的 collection 类可以通过 collectionClass bean 属性覆盖。 |
ApplicationEventMulticaster | 接口 | 接口由可以管理许多 ApplicationListener 对象并向其发布事件的对象实现。一个 org.springframework.context。 通常是一个 Spring ApplicationEventPublisher,可以使用ApplicationEventMulticaster 作为实际发布事件的委托。 |
ApplicationStartingEvent | 类 | 在 Environment 或 ApplicationContext 可用之前,但在 ApplicationListener 注册之后,尽可能早地发布事件(只要启动了 SpringApplication)。 事件的来源是 SpringApplication 本身,但是要注意在这个早期阶段过多地使用它的内部状态,因为它可能在生命周期的后期被修改。 |
SpringApplicationEvent | 抽象类 | 与一个 SpringApplication 相关的 ApplicationEvent的基类。 |
ApplicationEvent | 抽象类 | 派生所有事件状态对象的根类。所有事件都是通过对对象(源)的引用构造的,该对象在逻辑上被认为是事件最初发生的对象。 |
EventObject | 类 | 派生所有事件状态对象的根类。 所有事件都是由对对象的引用构造而成,该对象即“源”,逻辑上被认为是事件最初发生时的对象。 |
Q.E.D.
Comments | 1 条评论