一、如何启动
@SpringBootApplication
public class StarterApplication {
public static void main(String[] args) {
SpringApplication.run(StarterApplication.class, args);
}
}
最基本的Spring Boot应用只需要一个注解@SpringBootApplication、一行代码SpringApplication.run(class, args)即可启动。
其中,SpringApplication.run(class, args)的两个参数:
- class,启动类
- args,启动应用时的命令行参数
二、启动流程
进入SpringApplication.run方法,该方法很简单,只有一行代码:
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
主要做了两件事情:
- 构造SpringApplication对象
- 执行run方法,运行程序
1、构造SpringApplication
基本流程:
核心代码:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 启动类
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 根据当前程序中包含的类的类型,判断当前环境的的web类型:非web程序,servlet容器,响应式容器
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 启动上下文注册器,用于向启动上下文中注册一些对象
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// 应用上下文自定义初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 事件监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 根据当前栈信息中的main方法,判断应用主方法所在的类
this.mainApplicationClass = deduceMainApplicationClass();
}
默认情况下,事件监听器主要有以下几个:
除了事件监听器以外,还有两个比较重要的组件:
BootstrapRegistryInitializer,用于向启动上下文(即BootstrapContext,也有些资料称其为引导上下文)注册InstanceSupplier,一个用于创建实例对象的工厂方法。但是要注意的是该工厂方法仅支持“单例模式”,每个类型的工厂方法只能注册一个;ApplicationContextInitializer,初始化应用上下文的时候,执行一些自定义的初始化逻辑,此处的应用上下文即指Spring容器环境;
2、执行run方法
基本流程:
核心代码:
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
// 创建启动上下文,该上下文对象,仅在spring boot启动阶段会使用到。在启动后会关闭,仅留应用上下文对象存在,即下面的ConfigurableApplicationContext对象
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
// 配置awt配置,headless模式
configureHeadlessProperty();
// 获取生命周期监听器,通过该对象,执行生命周期事件回调
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass); // 生命周期-starting
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 环境变量相关扩展,但是此处仅仅只是读取了相关的扩展点信息,并没有将其加载到应用上下文中
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
// 准备banner信息
Banner printedBanner = printBanner(environment);
// 仅创建上下文,但是内部并没有创建bean
context = createApplicationContext();
// 标志启动阶段
context.setApplicationStartup(this.applicationStartup);
// 实际上此处才将读取的环境变量的信息进行了装载
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// kp spring核心流程,刷新上下文,创建bean
refreshContext(context);
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup); // 生命周期回调
// 触发ApplicationRunner与CommandLineRunner
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
// 返回context对象。
// 所以获取context对象除了ApplicationContextAware接口以外,也可以通过启动方法的返回值获取
return context;
}
其中有两个方法需要再详细看下,一是加载环境变量、配置的prepareEnvironment方法,一是配置应用上下文的prepareContext方法。
3、prepareEnvironment
最核心的是支持通过EnvironmentPostProcessor,加载自定义的变量配置
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
// kp 此处会发布ApplicationEnvironmentPreparedEvent事件,通知到EnvironmentPostProcessorApplicationListener对象。
// 该对象负责处理注册的EnvironmentPostProcessor,该扩展点支持用于自定义环境变量等配置
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
4、prepareContext
需要特别注意的是,在prepareEnvironment方法中加载到的变量、配置信息,实际上到执行prepareContext的时候,才加载到应用上下文中。
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
context.setEnvironment(environment); // 装载环境变量信息
postProcessApplicationContext(context); // 对上下文对象在refresh之前进行任意的后置处理,,扩展点。可以通过自定义SpringApplication实现扩展
applyInitializers(context); // 上下文初始化器,自定义一些初始化操作
listeners.contextPrepared(context);
bootstrapContext.close(context); // 关闭启动上下文 -- 两个上下文:启动上下文,,应用上下文
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
// spring 两个重要参数
// 1. 循环引用;2. bean覆盖
((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
}
// 懒加载配置
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// 支持PropertySource通过order注解指定顺序
context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
// 加载bean
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
listeners.contextLoaded(context);
}
核心操作:
- 执行应用上下文初始化器,即在构建
SpringApplication对象时,注册的ApplicationContextInitializer - 关闭启动上下文
BootstrapContext - 加载变量配置信息
- 应用上下文部分参数配置
三、获取上下文
Spring提供了ApplicationContextAware接口,通过实现该接口,可以获取到应用上下文对象。除了该方式还有别的方法吗?
仔细观察SpringApplication.run方法,实际上它是有返回值的:
这个对象就是应用上下文:
@SpringBootApplication
@Slf4j
public class StarterApplication implements ApplicationContextAware {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(StarterApplication.class, args);
// 此处获取的context对象,是应用上下文对象,与通过ApplicationContextAware获取到的上下文对象是同一个
log.info("method return context: {}", context);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
log.info("ApplicationContextAware context: {}", applicationContext);
}
}
执行结果:
从日志中可以看出是同一个对象。