一: SpringApplication构造方法
1.首先SpringBoot项目启动类来看
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//从SpringApplication.run方法中获取取到ApplicationContext上下文对象, 同时启动SpringBoot项目
ApplicationContext ctx = SpringApplication.run(DemoApplication.class, args);
}
}
2.看一下SpringApplication构造方法
public SpringApplication(Class... primarySources) {
this((ResourceLoader)null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
this.sources = new LinkedHashSet();
this.bannerMode = Mode.CONSOLE;//控制banner展示类型
this.logStartupInfo = true; //控制启动日志输出
this.addCommandLineProperties = true; //控制是否添加命令行配置
this.headless = true; //控制项目以headless模式启动(无gui和键盘鼠标)
this.registerShutdownHook = true; //控制是否注册虚拟机关闭监听
this.additionalProfiles = new HashSet();
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//保存启动的类
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
//确定当前运行的web环境
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//类路径下找到META‐INF/spring.factories配置的所有ApplicationContextInitializer;然后保存起来
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 从类路径下找到META‐INF/spring.factories配置的所有ApplicationListener保存起来
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
//推断出启动类class用于日志输出
this.mainApplicationClass = this.deduceMainApplicationClass();
}
① deduceWebApplicationType: 推断web应用类型
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;
}
}
以上代码中可以看出:
如果org.springframework.web.reactive.DispatcherHandler能够被加载且org.springframework.web.servlet.DispatcherServlet不能够被加载,那么断定web应用类型是REACTIVE;如果javax.servlet.Servlet和org.springframework.web.context.ConfigurableWebApplicationContext任意一个不能被加载,那么断定web应用类型是NONE;如果不能断定是REACTIVE和NONE,那么就是SERVLET类型;具体这三种类型代表什么含义,大家可以查看WebApplicationType中的说明。
② getSpringFactoriesInstances 从字面意思看就是获取spring工厂实例,至于从哪获取哪些工厂实例,我们往下看。
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 获取指定类型的工厂名字
Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 根据名字、类型创建工厂实例
List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
//对工厂实例进行排序,然后返回排序后的实例列表
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
loadFactoryNames源码中引用了loadSpringFactories方法:
loadSpringFactories做了以下这些事
a、 查找类路径下全部的META-INF/spring.factories的URL
b、 根据url加载全部的spring.factories中的属性
c、 将所有spring.factories中的值缓存到SpringFactoriesLoader的cache中:
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
方便下次调用。加载完所有的工厂名称之后,然后从中获取指定工厂类型的工厂名称列表,也就是
getOrDefault(factoryClassName, Collections.emptyList())做的事
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
// 遍历全部的URL,逐个读取META-INF/spring.factories中的属性
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryClassName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryName = var9[var11];
属性全部放入MultiValueMap<String, String> result中,注意result的类型
result.add(factoryClassName, factoryName.trim());
}
}
}
// 结果放入缓存,方便下次查找
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
总结:
①.定义启动需要的一些属性
②. 判断是否为一个web环境
③.类路径下找到META‐INF/spring.factories配置的所有
ApplicationContextInitializer
(是一个用来初始化Spring ConfigurableApplicationContext应用上下文的回调接口,
设定的调用时机是在ConfigurableApplicationContext#refresh()调用之前。
该接口典型的应用场景是web应用中需要编程方式对应用上下文做初始化。
比如,注册属性源(property sources)或者针对上下文的环境信息environment激活相应的profile)
和
ApplicationListener(在使用过程中可以监听某一事件的发生,可以做出相应的处理);然后保存起来
二:SpringApplication.run() 方法:
构造函数初始化环境之后就开始调用run方法启动容器
public ConfigurableApplicationContext run(String... args) {
// 秒表,用于记录启动时间;记录每个任务的时间,最后会输出每个任务的总费时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// spring应用上下文,也就是我们所说的spring根容器
ConfigurableApplicationContext context = null;
// 自定义SpringApplication启动错误的回调接口
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
//设置当前是否为headless启动
this.configureHeadlessProperty();
//获取spring.factories中的监听器变量,args为指定的参数数组,默认为当前类SpringApplication
// 获取启动时监听器(EventPublishingRunListener实例)
SpringApplicationRunListeners listeners = this.getRunListeners(args);
//触发ApplicationStartingEvent事件,启动监听器会被调用,一共5个监听器被调用,
//但只有两个监听器在此时做了事
listeners.starting();
Collection exceptionReporters;
try {
// 参数封装,也就是在命令行下启动应用带的参数,如--server.port=9000
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//1、加载外部化配置的资源到environment;2、触发ApplicationEnvironmentPreparedEvent事件
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
// 配置spring.beaninfo.ignore,并添加到名叫systemProperties的PropertySource中;默认为true即开启
this.configureIgnoreBeanInfo(environment);
//就是我们经常说的打印的那个spring的日志,大家可以写一个文件替换它。
Banner printedBanner = this.printBanner(environment);
//创建应用上下文,并实例化了其三个属性:reader、scanner和beanFactory
context = this.createApplicationContext();
// 获取异常报道器,即加载spring.factories中的SpringBootExceptionReporter实现类
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
// 准备应用上下文,
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新应用上下文并注册关闭钩子
this.refreshContext(context);
//空方法
this.afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
}
// 应用上下文准备完成,最终调用的是ApplicationStartedEvent:spring boot 启动监听类
listeners.started(context);
//调用Spring容器中的ApplicationRunner和CommandLineRunner接口的实现类
this.callRunners(context, applicationArguments);
} catch (Throwable var10) {
// 应用上下文准备遇到异常时,
// 向各个SpringApplicationRunListener发送事件finished, 携带响应异常信息
this.handleRunFailure(context, var10, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
// 最终调用的是ApplicationReadyEvent:上下文已经准备完毕的时候触发
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
ConfigurableApplicationContext类继承图
1. 设置java.awt.headless模式,来进行简单的图像处理,无需调用server设备,
很好解决linux环境下可能没有图形处理或者设备工具而带来的报错。这也是前面初始化SpringApplication的时候将
java.awt.headless设置为true的原因。
2.getRunListeners
我们先看看SpringApplicationRunListeners和SpringApplicationRunListener。
SpringApplicationRunListeners的类注释很简单:
一个存SpringApplicationRunListener的集合,里面有些方法,后续都会讲到;
SpringApplicationRunListener的接口注释也简单:
监听SpringApplication的run方法。通过SpringFactoriesLoader加载SpringApplicationRunListene(一个或多个),SpringApplicationRunListener的实现类必须声明一个接收SpringApplication实例和String[]数组的公有构造方法。
接下来我们看看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));
}
里面的getSpringFactoriesInstances在SpringApplication的构造方法中调用了两次,分别用来设置属性List<ApplicationContextInitializer<?>> initializers和List<ApplicationListener<?>> listeners。getSpringFactoriesInstances在第一次被调用时会将类路径下所有的META-INF/spring.factories的文件中的属性进行加载并缓存到SpringFactoriesLoader的缓存cache中,下次被调用的时候就直接从SpringFactoriesLoader的cache中取数据了。这次就是从SpringFactoriesLoader的cache中取SpringApplicationRunListener类型的类(全限定名),然后实例化后返回。我们来跟下这次getSpringFactoriesInstances获取的的内容
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 = Thread.currentThread().getContextClassLoader();
Set<String> names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = this.createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
可以看到names里面的内容:
就是获取SpringApplicationRunListener类型的实例(EventPublishingRunListener对象),并封装进SpringApplicationRunListeners对象,然后返回这个SpringApplicationRunListeners对象。说的再简单点,getRunListeners就是准备好了运行时监听器EventPublishingRunListener。
3.listeners.starting(),我们看看starting方法做了些什么事
首先会调用SpringApplicationRunListeners.starting(),其次会调用EventPublishingRunListener.starting()
public void starting() {
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}
看一下multicastEvent方法:
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
Iterator var4 = this.getApplicationListeners(event, type).iterator();
while(var4.hasNext()) {
ApplicationListener<?> listener = (ApplicationListener)var4.next();
Executor executor = this.getTaskExecutor();
if (executor != null) {
executor.execute(() -> {
this.invokeListener(listener, event);
});
} else {
this.invokeListener(listener, event);
}
}
}
初略的看,就是遍历getApplicationListeners(event, type),然后对每个listener进行invokeListener(listener, event)
①getApplicationListeners(event, type)方法:
根据其注释可知,该方法作用:返回与给定事件类型匹配的ApplicationListeners集合,非匹配的侦听器会被提前排除;允许根据缓存的匹配结果来返回。
getApplicationListeners方法过滤出来的监听器包括LoggingApplicationListener、BackgroundPreinitializer、DelegatingApplicationListener、LiquibaseServiceLocatorApplicationListener、EnableEncryptablePropertiesBeanFactoryPostProcessor五种类型的对象。这五个对象的onApplicationEvent都会被调用。
那么这五个监听器的onApplicationEvent都做了些什么了,我这里大概说下,细节的话大家自行去跟源码
LoggingApplicationListener:初始化日志系统,默认是logback,支持3种,优先级从高到低:logback > log4j > javalog
BackgroundPreinitializer:启动多个线程执行相应的任务,包括验证器、消息转换器等等
DelegatingApplicationListener:此时什么也没做
LiquibaseServiceLocatorApplicationListener:此时什么也没做
EnableEncryptablePropertiesBeanFactoryPostProcessor:仅仅打印了一句日志,其他什么也没做
②invokeListener其注释:使用给定的事件调用给定的监听器
4. prepareEnvironment按字面意思就是准备环境
// 准备环境
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment 创建和配置环境
// 获取或创建环境 ,web环境或者非web环境
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 配置环境:配置PropertySources和activeProfiles
configureEnvironment(environment, applicationArguments.getSourceArgs());
// listeners环境准备(就是广播ApplicationEnvironmentPreparedEvent事件)。还记得这个listeners怎么来的吗?
listeners.environmentPrepared(environment);
// 将环境绑定到SpringApplication
bindToSpringApplication(environment);
// 如果是非web环境,将环境转换成StandardEnvironment
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
// 配置PropertySources对它自己的递归依赖
ConfigurationPropertySources.attach(environment);
return environment;
}
5.createApplicationContext()方法:
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);
}
根据SpringApplication的webApplicationType来实例化对应的上下文;如果webApplicationType的值是SERVLET,那么实例化AnnotationConfigServletWebServerApplicationContext,如果是REACTIVE则实例化AnnotationConfigReactiveWebServerApplicationContext(响应式编程,后续再看),如果既不是SERVLET、也不是REACTIVE,那么则是默认情况(也就是我们所说的非web引用),实例化AnnotationConfigApplicationContext。
AnnotationConfigServletWebServerApplicationContext类图
AnnotationConfigServletWebServerApplicationContext构造方法
public AnnotationConfigServletWebServerApplicationContext() {
this.reader = new AnnotatedBeanDefinitionReader(this); // 实例化注解bean定义读取器
this.scanner = new ClassPathBeanDefinitionScanner(this); // 实例化类路径bean定义扫描器
}
AnnotatedBeanDefinitionReader从类注释上来看,作用就是用于编程式注解bean的注册,
例如我们平时用到的 @Component,还有@Configuration类下的@Bean等
用来加载class类型的配置,在它初始化的时候,会预先注册一些BeanPostProcessor和BeanFactoryPostProcessor,这些处理器会在接下来的spring初始化流程中被调用。ClassPathBeanDefinitionScanner是一个扫描指定类路径中注解Bean定义的扫描器,在它初始化的时候,会初始化一些需要被扫描的注解。
DefaultListableBeanFactory,也就是我们所说的beanFactory,用来注册所有bean定义(bean definitions),也可以用来作为单例bean工厂。
ClassPathBeanDefinitionScanner是类路径bean定义扫描器,用于检测类路径上的bean候选者。
6.SpringBootExceptionReporter是一个回调接口,用于支持对SpringApplication启动错误的自定义报告。
7. prepareContext()方法 准备应用上下文
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 设置上下文的environment
context.setEnvironment(environment);
// 应用上下文后处理
postProcessApplicationContext(context);
// 在context refresh之前,对其应用ApplicationContextInitializer
applyInitializers(context);
// 上下文准备(目前是空实现,可用于拓展)
listeners.contextPrepared(context);
// 打印启动日志和启动应用的Profile
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
// 向beanFactory注册单例bean:命令行参数bean
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
// 向beanFactory注册单例bean:banner bean
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// Load the sources
// 获取全部资源,其实就一个:SpringApplication的primarySources属性
Set<Object> sources = getAllSources();
// 断言资源是否为空
Assert.notEmpty(sources, "Sources must not be empty");
// 将bean加载到应用上下文中
load(context, sources.toArray(new Object[0]));
// 向上下文中添加ApplicationListener,并广播ApplicationPreparedEvent事件
listeners.contextLoaded(context);
}
看名字就能想到是准备上下文,也就是往我们上一步生成的 AnnotationConfigServletWebServerApplicationContext上下文中继续填充属性。这里方法较多,主要是将context中的environment替换成SpringApplication中创建的environment,将SpringApplication中的initializers应用到context中,将SpringApplication中的listeners注册到context中,加载两个单例bean到beanFactory中,初始化资源加载器BeanDefinitionLoader,并广播ApplicationPreparedEvent事件,触发相应的监听器
ConfigFileApplicationListener
添加名叫random的RandomValuePropertySource到environment
添加名叫applicationConfig:[classpath:/application.yml]的OriginTrackedMapPropertySource到environment
LoggingApplicationListener 初始化日志系统
加载外部化配置的资源到environment,主要是application.yml(.yaml/.xml/.properties)等
ApplicationReadyEvent :上下文已经准备ok,这个时候就可以通过ApplicationReadyEvent获取
ConfigurableApplicationContext,然后通过ConfigurableApplicationContext 获取bean的信息。
ApplicationStartedEvent:spring boot 启动监听类。
可以在SpringApplication启动之前做一些手脚,比如修改SpringApplication实例对象中的属性值
三.总结Spring boot应用
三个事件
ApplicationStartingEvent
在监听器注册完、SpringApplication构造完后,以及其他的任何处理之前被广播,触发对应的事件监听器
ApplicationEnvironmentPreparedEvent
environment创建后,context创建之前被广播,触发对应的事件监听器 ApplicationPreparedEvent
bean定义加载后,上下文refresh之前被广播,触发对应的事件监听器
后续还会涉及到ApplicationReadyEvent、ApplicationFailedEvent事件
三个核心
从spring.factories加载一系列的类,包括Initializer、ApplicationListener、AutoConfigure、Failure analyzers等等,springboot的自动配置,从此时已经开始了,一系列的AutoConfigure都是从spring.factories获取的。
1. environment:StandardServletEnvironment
表示当前应用程序所处的环境,主要包括两方面:profiles和properties;
例如我们经常说的本地、运测、预发布、生产环境,就可以通过environment进行配置,以及是否是web环境。
一般而言,我们的环境是StandardServletEnvironment,标准的servlet环境,也就是我们经常说的web环境
2. ApplicationContext:AnnotationConfigServletWebServerApplicationContext
应用上下文,用于为应用程序提供配置的中央接口,提供如下内容:
1、访问应用程序组件的Bean工厂方法
2、加载文件资源的能力
3、发布事件到已注册的事件监听器的能力
4、解析消息,支持国际化的能力
等等一系列的功能
3.AnnotationConfigServletWebServerApplicationContext是springboot对spring应用上下文的拓展,
引入了一些springboot的内容。