前言
本文从spring启动源码开始解读,过程中附带一些额外spring相关的知识点.
1. new SpringApplication()
//1.main方法,spring开始的地方
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
//2.run方法
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
//3.这里new了一个SpringApplication,继续run方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
我们看一下new SpringApplication()
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 【1】给resourceLoader属性赋值,注意传入的resourceLoader参数为null
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 【2】给primarySources属性赋值,传入的primarySources其实就是SpringApplication.run(MainApplication.class, args);中的MainApplication.class
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 【3】给webApplicationType属性赋值,根据classpath中存在哪种类型的类来确定是哪种应用类型,默认WebApplicationType.SERVLET,后续会在prepareEnvironment以及createApplicationContext方法里用到(想想也是,不同的应用类型,创建的环境和context肯定也不会一样)
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 【4】★★给initializers属性赋值,利用SpringBoot自定义的SPI从spring.factories中加载ApplicationContextInitializer接口的实现类并赋值给initializers属性,后续在prepareContext()中调用
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 【5】★★给listeners属性赋值,利用SpringBoot自定义的SPI从spring.factories中加载ApplicationListener接口的实现类并赋值给listeners属性.ApplicationListener主要是用来实现各种applicationEvent的监听,需配合applicationRunListener发布事件
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 【6】给mainApplicationClass属性赋值,即这里要推断哪个类调用了main函数,然后再赋值给mainApplicationClass属性,用于后面启动流程中打印一些日志。
this.mainApplicationClass = deduceMainApplicationClass();
}
1.1扩展 Spring通过SPI加载ApplicationContextInitializer和ApplicationListener
重点看下getSpringFactoriesInstances(ApplicationContextInitializer.class)方法
//type就是ApplicationContextInitializer.class
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
//获取classloader,常规就是appclassloader
ClassLoader classLoader = getClassLoader();
// 重点看下 SpringFactoriesLoader.loadFactoryNames
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 用反射生成刚刚加载类的实例
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
//排个序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
关键方法是SpringFactoriesLoader.loadFactoryNames(),就是在这里加载的spring.factories里的配置
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
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 ?
//加载classpath下的META-INF/spring.factories
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);
//读取到spring.factories内容
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);
}
}
以下是spring.factories里的内容
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
其中initializer和listeners都是从spring.factories中加载(可能有多个spring.factories)
1.2 SPI机制
SPI(Service Provider Interface),JDK内置的服务发现机制,主要思想是解耦,将装配的控制权转移到程序之外,在模块化设计十分有用.
SPI流程:
- 有关组织和公式定义接口标准
- 第三方提供具体实现: 实现具体方法, 配置 META-INF/services/${interface_name} 文件
- 开发者使用
使用示例:
public interface SPIInterface {
public void helloWorld();
}
//可以在各自的jar包中定义接口实现类
public class JavaSPIImpl implements SPIInterface {
@Override
public void helloWorld() {
System.out.println("JAVA helloworld");
}
}
public class PHPSPIImpl implements SPIInterface {
@Override
public void helloWorld() {
System.out.println("PHP helloworld");
}
}
在META-INF中新建services文件夹,新建spi接口类名文件(全路径),写入实现类.
1.3 SPI使用场景
在我司一个外部项目中,有部分功能需要甲方实现,可以使用SPI机制让甲方写实现类,然后集成到我司项目中
提问:为啥不用接口调用?
回答:当然可以用接口调用,甲方提供接口,我们的代码中调用,但是此项目只是一个甲方一个外围项目,甲方不希望直接和他们的核心系统对接,所以这部分代码我们乙方基本不参与,也就是说甲方想直接自己二开该项目
提问:还有哪些场景可以用SPI吗
回答:SPI的场景使用确实不多,SPI产生年代久远,可能不太完全适用现在的开发要求,常规的场景是JDBC兼容各类厂家的数据库,还有一些就是项目中如果需要兼容多种中间件,可以使用spi,比如:FileStorageService对象存储,一般需要兼容阿里oss,华为OBS,七牛云.....万一以后又需要再兼容一套对象存储,可以使用类似JDBC的模式
2. run方法
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//设置无显示参数,不用关心
configureHeadlessProperty();
//加载runlistener
SpringApplicationRunListeners listeners = getRunListeners(args);
//这里会向每个ApplicationListener发送一个ApplicationStartedEvent
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//准备环境,通过发送environmentprepared的事件通知加载系统环境以及properties/yml文件
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//根据配置spring.beaninfo.ignore 忽略一些bean,不重要
configureIgnoreBeanInfo(environment);
//打印banner
Banner printedBanner = printBanner(environment);
//创建applicationcontext,主要为beanfactory-DefaultListableBeanFactory
context = createApplicationContext();
//实例化SpringBootExceptionReporter,用于报告springboot启动的异常
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//核心重点1 ,刷新上下文的准备工作, 设置context的属性设置,bean对象的创建(包含Application的启动类创建)
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//核心重点2 加载bean以及各种配置
refreshContext(context);
//刷新后处理,该方法是一个扩展方法,没有提供任何默认的实现,我们自定义的子类可以进行扩展。
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//应用SpringApplicationRunListener监听器的started方法
listeners.started(context);
//执行容器中的ApplicationRunner和CommandLineRunner类型的bean的run方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
3. 启动runListener
从spring.factories中加载SpringApplicationRunListener, 内部有一个initialMulticaster, 用于广播event给applicationListeners
// 【1】从spring.factories配置文件中加载到EventPublishingRunListener对象并赋值给SpringApplicationRunListeners
// EventPublishingRunListener对象主要用来发射SpringBoot启动过程中内置的一些生命周期事件,标志每个不同启动阶段
SpringApplicationRunListeners listeners = getRunListeners(args);
// 启动SpringApplicationRunListener的监听,表示SpringApplication开始启动。
// 》》》》》发射【ApplicationStartingEvent】事件
listeners.starting();
3 prepareEnvironment
首先,准备环境变量,包含系统变量、环境变量、命令行参数、默认变量、servlet相关配置、配置文件等
其次, 发射 environmentPrepared 事件
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
//new 了一个StandardServletEnviroment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 这里为之前创建的environment配置一些命令行参数形式的环境变量以及系统环境变量、JVM环境变量
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 利用事件监听机制来为environment环境变量配置application.properties中的环境变量或@{}形式的环境变量
//主要通过ConfigFileApplicationListener事件监听来加载配置properties及yml文件
listeners.environmentPrepared(environment);
bindToSpringApplication(environment); // TODO 将environment绑定在SpringApplication,后续用来干嘛呢?
if (!this.isCustomEnvironment) {
// TODO 这里什么情况下需要进行转换?
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
listeners中有一个 ConfigFileApplicationListener ,主要用于配置properties以及yml文件
springboot2.2.9中配置文件的加载顺序为:
* <ul>
* <li>file:./config/</li>
* <li>file:./</li>
* <li>classpath:config/</li>
* <li>classpath:</li>
* </ul>
4. createApplicationContext
根据不同类型创建不同类型的spring applicationcontext容器 因为这里是servlet环境,所以创建的是AnnotationConfigServletWebServerApplicationContext容器对象 applicationContext使用反射的方式创建
//构造方法
public AnnotationConfigServletWebServerApplicationContext() {
//这里会注册一些beanDefinition,主要为注解增强器
//1.internalConfigurationAnnotationProcessor处理@Component、@CompoenentScan等注解
//2.internalAutowiredAnnotationProcessor处理@Autowired、@Inject、@Valued等注解
//3.internalCommonAnnotationProcessor
//4.internalEventListenerProcessor
//5.internalEventListenerFactory
this.reader = new AnnotatedBeanDefinitionReader(this);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
看下这个类图,其中关键的是AnnotationConfigServletWebServerApplicationContext间接集成BeanDefinitionRegistry、ResourceLoader、EnvironmentCapable以及BeanFactory等接口
- BeanDefinitionRegistry: 主要是beandefinition的注册,获取,增删等功能
- ResourceLoader: 主要为资源读写
- BeanFactory: bean的获取,移除等
- EnvironmentCapable: 环境变量读写能力
可以看出ApplicationContext 就是个大杂烩,集合多方功能,因为其采用了门面模式
5.获取SpringBootExceptionReporter
从spring.factories配置文件中加载异常报告期实例,这里加载的是FailureAnalyzers 注意FailureAnalyzers的构造器要传入ConfigurableApplicationContext,因为要从context中获取beanFactory和environment
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
6. ★★ prepareContext
为刚创建的AnnotationConfigServletWebServerApplicationContext容器对象做一些初始化工作,准备一些容器属性值等
- 为AnnotationConfigServletWebServerApplicationContext的属性AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner设置environgment属性
- 根据情况对ApplicationContext应用一些相关的后置处理,比如设置resourceLoader属性等
- 在容器刷新前调用各个ApplicationContextInitializer的初始化方法,ApplicationContextInitializer是在构建SpringApplication对象时从spring.factories中加载的
- 》》》》》发射【ApplicationContextInitializedEvent】事件,标志context容器被创建且已准备好
- 从context容器中获取beanFactory,并向beanFactory中注册一些单例bean,比如applicationArguments,printedBanner
- TODO 加载bean到application context,注意这里只是加载了部分bean比如mainApplication这个bean
- 》》》》》发射【ApplicationPreparedEvent】事件,标志Context容器已经准备完成
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//设置环境变量
context.setEnvironment(environment);
//增强applicationcontext,就是设置bean名称生成器、资源加载器以及转换器
postProcessApplicationContext(context);
//应用初始化器,之前有加载的
applyInitializers(context);
//发射事件 contextPrepared
listeners.contextPrepared(context);
//是否打印启动相关日志,默认true
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// 获取此上下文内部的beanFactory,即bean工厂
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//将applicationArguments作为一个单例对象注册到bean工厂中
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
// 默认就是DefaultListableBeanFactory
if (beanFactory instanceof DefaultListableBeanFactory) {
//设置是否允许同名bean的覆盖,这里默认false,如果有同名bean就会抛出异常
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
//设置是否允许懒加载,即延迟初始化bean,即仅在需要bean时才创建该bean,并注入其依赖项。
//默认false,即所有定义的bean及其依赖项都是在创建应用程序上下文时创建的。
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// 加载启动源,默认就是我们的启动类
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
/*
* 加载beanDefinition,最终执行的是BeanDefinitionLoader
* 加载所有的beanDefinition
*/
load(context, sources.toArray(new Object[0]));
//发射contextloaded事件
listeners.contextLoaded(context);
}
7. ★★★★ refreshContext
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 1.在context刷新前做一些准备工作,比如初始化一些属性设置,属性合法性校验和保存容器中的一些早期事件等
prepareRefresh();
// 2.让子类刷新其内部bean factory,注意SpringBoot和Spring启动的情况执行逻辑不一样,springboot是直接获取beanfactory,因为在springboot启动过程中createApplicationContext已经初始化了beanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3.对bean factory进行配置,比如配置bean factory的类加载器,后置处理器等
prepareBeanFactory(beanFactory);
try {
// 4.执行一些后置处理逻辑,子类通过重写这个方法来在BeanFactory创建并预准备完成以后做进一步的设置;在springBoot里设置了一个awareProcessor
postProcessBeanFactory(beanFactory);
//----------------- 至此beanFactory的所有准备工作均已完成-------
// 5.执行BeanFactoryPostProcessor的方法即调用bean factory的后置处理器; 加载所有的beanDefinition; springBoot中从启动Application类入手循环解析@component,内部类bean,扫描componentScan标识的包,引入@import类(触发registra以及selectImportor),最后处理@bean;
invokeBeanFactoryPostProcessors(beanFactory);
//----------------- 核心扫描beanDefinination结束-------
// 6.注册bean的后置处理器BeanPostProcessor,注意不同接口类型的BeanPostProcessor
registerBeanPostProcessors(beanFactory);
// 7. 初始化国际化MessageSource相关的组件,比如消息绑定,消息解析等
initMessageSource();
// 8. 初始化事件广播器,如果bean factory没有包含事件广播器,那么new一个SimpleApplicationEventMulticaster广播器对象并注册到bean factory中
initApplicationEventMulticaster();
// 9.AbstractApplicationContext定义了一个模板方法onRefresh,留给子类覆写,比如ServletWebServerApplicationContext覆写了该方法来创建内嵌的tomcat容器
onRefresh();
// 10.注册实现了ApplicationListener接口的监听器,之前已经有了事件广播器,此时就可以派发一些early application events
registerListeners();
// 11.完成容器bean factory的初始化,并初始化所有剩余的单例bean。这一步非常重要,一些bean postprocessor会在这里调用。
finishBeanFactoryInitialization(beanFactory);
// 12.完成容器的刷新工作,并且调用生命周期处理器的onRefresh()方法,并且发布ContextRefreshedEvent事件
finishRefresh();
//----------------- bean的初始化结束-------
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
8. afterRefresh
后置增强方法,目前为空
9. 发射started事件
发射【ApplicationStartedEvent】事件,标志spring容器已经刷新,此时所有的bean实例都已经加载完毕
listeners.started(context);
10. 调用Runner
调用ApplicationRunner和CommandLineRunner的run方法,实现spring容器启动后需要做的一些东西比如加载一些业务数据等
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}