使用的 springboot 源码版本 2.3.12.RELEASE,这是2.3.x系列的最后一个版本。
springboot应用的启动方式很多,可以在引导类中配置 ConfigurableApplicationContext,可以继承 SpringBootServletInitializer 重写SpringApplication的配置,可以用 SpringApplicationBuilder 来构建SpringApplication…不同的启动方式、不同版本的源码,启动过程、调用的一些方法有所差别,整体过程大同小异,此处以默认的启动方式进行讲述。
文中涉及到的Banner、SpringApplicationRunListener、Runner、StopWatch 的使用方式,可参考我的另一篇博文
https://blog.csdn.net/chy_18883701161/article/details/120753879
引导类
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
//调用 SpringApplication 的静态方法 run() 进行启动
SpringApplication.run(DemoApplication.class, args);
}
}
实质是调用 SpringApplication 本身的另一个重载的静态 run() 方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
核心只有一句代码 new SpringApplication(primarySources).run(args)
,2个步骤
springboot的启动思路
有了上面的核心思路,我们完全可以自己写main()方法中的启动代码
// 默认的启动代码
// SpringApplication.run(DemoApplication.class, args);
//创建实例,传入基础资源(引导类)
SpringApplication springApplication = new SpringApplication(DemoApplication.class);
// 甚至可以对SpringApplication实例进行配置,比如添加应用上下文初始化器
// springApplication.addInitializers(new MyApplicationContextInitializer());
//调用实例的run()方法进行启动,传入main()方法的参数
springApplication.run(args);
这种启动方式很常用,经常需要在创建 SpringApplication 实例后、调用实例 run() 方法启动应用之前,对 SpringApplication 实例进行配置。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//设置资源加载器 resourceLoader
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//设置要加载的基础资源 primarySources
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//推断应用类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//设置应用上下文的初始化器 ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
//设置应用监听器 ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//推断主类
this.mainApplicationClass = deduceMainApplicationClass();
}
主要是初始化 SpringApplication 的一些成员变量,那2个setter方法其实是 this.setXxx();
省略了this
//基础资源
private Set<Class<?>> primarySources;
基础资源指的是springboot应用中自定义的一些核心类,通常是引导类。所谓加载基础资源,其实就是处理这些核心类,让这些核心类生效,比如解析引导类上的 @SpringBootApplication、@MapperScan,使之生效。
//默认的启动代码:第一个参数指定的就是 primarySources
SpringApplication.run(DemoApplication.class, args);
拓展问题:SpringBoot中最核心的类是哪个?
枚举类 WebApplicationType
public enum WebApplicationType {
/**
* 不是web应用,不会使用内嵌的web容器来启动
*/
NONE,
/**
* 基于servlet的web应用,会使用内嵌的servlet容器启动
*/
SERVLET,
/**
* 基于reactive的web应用,会使用内嵌的reactive容器启动
*/
REACTIVE;
//servlet、reactive的核心类,用于帮助推断应用类型
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework.web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org.springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext";
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext";
/**
* 默认的推断方式,检测classpath中是否有对应的核心类
*/
static WebApplicationType deduceFromClasspath() {
//使用ClassUtils.isPresent()判断 classpath中是否有servlet或reactive的核心类,是否能用类加载器进行加载
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
/**
* 另一种推断方式,检测使用的上下文类型是否是servlet、reactive体系的
*/
static WebApplicationType deduceFromApplicationContext(Class<?> applicationContextClass) {
//使用的上下文是否是 WebApplicationContext 或其子类,WebApplicationContext 是servlet体系的基础上下文
if (isAssignable(SERVLET_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
return WebApplicationType.SERVLET;
}
//使用的上下文是否是 ReactiveWebApplicationContext 或其子类
if (isAssignable(REACTIVE_APPLICATION_CONTEXT_CLASS, applicationContextClass)) {
return WebApplicationType.REACTIVE;
}
return WebApplicationType.NONE;
}
/**
* 参数分别是String形式、Class形式的类名,用于判断后者是否是前者本身或者其子类
*/
private static boolean isAssignable(String target, Class<?> type) {
try {
return ClassUtils.resolveClassName(target, null).isAssignableFrom(type);
}
catch (Throwable ex) {
return false;
}
}
}
这个枚举类主要
spring.factories 文件格式
#实质是 properties 文件,key是接口,value指定该接口要使用的实现类,可指定多个实现类,都是使用全限定类名
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
new SpringApplication()中的相关源码
private List<ApplicationContextInitializer<?>> initializers;
private List<ApplicationListener<?>> listeners;
//实质是 this.setXxx() 给SpringApplication实例对应的成员变量赋值
//先调用 getSpringFactoriesInstances() 根据 spring.factories 中的配置获取要使用的接口(应用监听器、初始化器)实例,再把这些实例赋给对应的成员变量
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
SpringApplication 的实例方法 getSpringFactoriesInstances()
用于根据 spring.factories 中的配置,获取指定接口要使用的各个实现类的实例
/**
* 根据 spring.factories 中的配置,获取指定接口要使用的各个实现类的实例
* (通过反射调用无参构造器创建实例)
*
* @param type 指定接口
* @return 指定接口要使用的各个实现类的实例
*/
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
//实质是调用下面的重载方法,注意这里传入了一个空数组
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
/**
* 根据 spring.factories 中的配置,获取指定接口要使用的各个实现类的实例
* (通过反射调用指定的造器器创建实例)
*
* @param type 指定接口
* @param parameterTypes 指定要使用的构造器的形参表的参数类型
* @param args 对应的实参
* @return 指定接口要使用的各个实现类的实例
*/
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
//获取类加载器:如果 this.resourceLoader 不为空则使用resourceLoader的类加载器,否则使用默认的类加载
ClassLoader classLoader = getClassLoader();
//加载、解析classpath中的所有spring.factories,得到指定接口要使用的各个实现类的全限定类名
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//给这些实现类创建实例
//实质是通过反射调用指定的构造方法创建实例,会根据传入的构造方法的参数类型 parameterTypes 自动确定要使用的构造器
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
看第1个 getSpringFactoriesInstances(), return getSpringFactoriesInstances(type, new Class>[] {});
传的是空数组,要使用的构造方法的形参表是空数组,即是调用实现类的无参构造器创建实例。
spring.factories 文件指定的实现类很多,这些实现类的构造方法多种多样,都是统一调用无参的构造方法创建实例。我们在使用 spring.factories 机制时,应该给指定的实现类提供无参的构造器。
SpringFactoriesLoader 的静态方法 loadFactoryNames()
用于根据 spring.factories 中的配置,获取指定接口要使用的各个实现类的(全限定)类名
/**
* 根据 spring.factories 中的配置,获取指定接口要使用的实现类的全限定类名列表
*
* @param factoryType 指定接口
* @param classLoader 要使用的类加载器,为null时会使用系统类加载器、引导类加载器|根加载器
* @return List,指定接口要使用的实现类的类名列表
*/
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
//从接口的Class对象获取接口的全限定类名
String factoryTypeName = factoryType.getName();
//先调用 loadSpringFactories() 加载、解析所有的 spring.factories
//再调用 getOrDefault() 从解析结果中获取指定接口要使用的实现类类名列表。如果解析结果中没有指定接口或该接口要使用的实现类为空,则返回指定的默认值(空集合)
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
/**
* 加载、解析classpath中所有的 META-INF/spring.factories 文件
*
* @param classLoader 要使用的类加载器,为null时会使用系统类加载器、引导类加载器|根加载器
* @return Map>,key是接口名,value是该接口要使用的实现类的类名列表,都是全限定的
*/
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
//先从缓存中获取,有就直接返回,没有才加载、解析 spring.factories
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
//使用类加载器加载 classpath中所有 META-INF/spring.factories ,得到各个 spring.factories 文件的路径
//如果传入的类加载器为null,则使用系统类加载器 SystemClassLoader,如果系统类加载器也为null,则使用根类加载器|引导类加载器 BootstrapClassLoader
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
// LinkedMultiValueMap,MultiValue 顾名思义 一个key可以对应多个value
// 内部用 Map> 存储数据,add(key,value)添加元素时,会把value放到对应的List中
// 用于存储所有spring.factories文件的解析结果,一个键值对即一个 接口 => 要使用的实现类
result = new LinkedMultiValueMap<>();
//遍历所有的spring.factories文件,逐个解析
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
//解析为 Properties
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
//遍历各个键值对
for (Map.Entry<?, ?> entry : properties.entrySet()) {
//key是接口名
String factoryTypeName = ((String) entry.getKey()).trim();
//value是包含多个实现类的字符串,切分为字符串数组进行遍历
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
//添加到 LinkedMultiValueMap 中
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);
}
}
从上面的分析很容易看出
第一次调用 getSpringFactoriesInstances() 时,就加载、解析了所有的 spring.factories,把解析结果放到了缓存中,但只实例化了指定接口要使用的实现类。
后续再调用 getSpringFactoriesInstances() 时,都是直接从缓存中获取解析结果( Map> ),从中获取指定接口要使用的实现类列表,对其进行实例化。
spring.factories文件只加载、解析了1次
扩展:SPI机制使用自定义的接口
spring.factories中指定的接口通常是作为框架本身提供的扩展配置,比如应用监听器 ApplicationListener、应用初始化器 ApplicationContextInitializer,框架本身会处理这些扩展配置、使之生效。
有时候需要对 spring.factories 中的接口配置做一些自定义的处理,比如要在 spring.factories 中给自定义的接口指定实现类,spring|springboot 本身不会处理这些自定义接口的配置,需要我们自行写代码处理。
SpringFactoriesLoader 是spring提供的处理spring.factories的工具类,常用方法如下
//参数都是指定接口、要使用的类加载器,类加载器可以为null
//获取指定接口要使用的实现类的类名列表
List<String> classNameList = SpringFactoriesLoader.loadFactoryNames(Xxx.class, null);
//获取指定接口要使用的各个实现类的实例
List<Xxx> instanceList = SpringFactoriesLoader.loadFactories(Xxx.class, null);
SpringApplication类是springboot提供的,也有几个处理 spring.factories 文件的方法,都是在 SpringFactoriesLoader 的基础上实现的,使用 private 修饰,只在SpringApplication内部使用,不暴露出去。
这个方法是 SpringApplication 的实例方法,根据方法调用栈找到主类
private Class<?> deduceMainApplicationClass() {
try {
//获取各个调用的栈帧,一个栈帧对应一个方法调用
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
//遍历这些栈帧
for (StackTraceElement stackTraceElement : stackTrace) {
//找到方法名为main的栈帧
if ("main".equals(stackTraceElement.getMethodName())) {
//把该栈帧对应的java类作为主类返回
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
实例run()方法用于启动spring应用,创建并刷新一个新的应用上下文
/**
* Run the Spring application, creating and refreshing a new ApplicationContext
*
* @param args main()方法传递的参数
* @return 一个已创建、启动的 ApplicationContext
*/
public ConfigurableApplicationContext run(String... args) {
//创建并启动计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
//配置系统属性 java.awt.headless
configureHeadlessProperty();
//创建运行监听器组
//先根据spring.factories中的配置创建要使用的运行监听器实例,再把这些运行监听器实例封装为运行监听器组
SpringApplicationRunListeners listeners = getRunListeners(args);
//执行运行监听器的starting()回调方法
listeners.starting();
try {
//构建应用参数
//应用参数指的是传给main()方法的参数 arg,这一步会将main()方法的参数封装为ApplicationArguments
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//准备环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
//配置 spring.beaninfo.ignore 属性
configureIgnoreBeanInfo(environment);
//打印banner
Banner printedBanner = printBanner(environment);
//创建应用上下文
context = createApplicationContext();
//准备上下文
//主要是对上一步创建好的上下文进行配置、完善,这一步会加载所有的bean定义
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//刷新上下文
//主要是调用高级容器的refresh()刷新上下文,这一步会实例化所有的单例bean(懒加载的除外)
refreshContext(context);
//执行刷新后处理
//这个是protected修饰的空方法,作为预留的扩展点可被子类重写,以在上下文刷新之后做一些自定义的操作
afterRefresh(context, applicationArguments);
//终止计时器,打印启动花费的时长
stopWatch.stop();
if (this.logStartupInfo) {
//日志示例:Started DemoApplication in 4.25 seconds (JVM running for 5.368)
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//执行运行监听器的 started() 回调方法
listeners.started(context);
//执行 Runner 回调
//这也是预留的一个扩展点,可以在应用启动后,根据main()参数做一些自定义的处理
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
//主要是:1执行运行监听器的 failed() 回调方法;2、使用异常报告器 SpringBootExceptionReporter 打印|记录异常信息
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
//执行运行监听器的 running() 回调方法
listeners.running(context);
}
catch (Throwable ex) {
//和上面的 handleRunFailure() 差不多,只是传入的运行监听器组是null,不会执行运行监听器的的failed()回调方法,只打印异常信息
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
//返回应用上下文
return context;
}
准确来说,应该叫做 启动监听器。
SpringApplicationRunListener 接口
这个监听器用于监听spring应用的启动事件,即监听 SpringApplication 的实例 run() 方法的执行过程,提供了一系列回调方法,可在run()方法执行 | 启动过程的特定阶段做一些额外操作
public interface SpringApplicationRunListener {
default void starting() {
}
default void environmentPrepared(ConfigurableEnvironment environment) {
}
default void contextPrepared(ConfigurableApplicationContext context) {
}
default void contextLoaded(ConfigurableApplicationContext context) {
}
default void started(ConfigurableApplicationContext context) {
}
default void running(ConfigurableApplicationContext context) {
}
default void failed(ConfigurableApplicationContext context, Throwable exception) {
}
}
注意:这个接口和 ApplicationListener 都是监听器的2个顶级接口,这2个接口之间没有继承关系。
ApplicationListener 应用监听器、SpringApplicationRunListener 运行监听器,都是监听器,但区别很大
EventPublishingRunListener 实现类
SpringApplicationRunListener 接口提供的都是默认方法,只提供了空实现,springboot只给这个接口提供了一个实现类 EventPublishingRunListener,用于在应用启动的各个阶段发布对应的事件
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
private final SpringApplication application;
private final String[] args;
private final SimpleApplicationEventMulticaster initialMulticaster;
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
//初始化应用事件广播器
this.initialMulticaster = new SimpleApplicationEventMulticaster();
//把之前创建的应用事件监听器实例添加到这个事件广播器上
// new SpringApplication() 创建 SpringApplication 实例时,只是创建了 ApplicationListener 的实例,这一步添加到事件广播器上,ApplicationListener才会生效,此能监听这个广播器发布的事件
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
@Override
public int getOrder() {
return 0;
}
@Override
public void starting() {
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
this.initialMulticaster
.multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
for (ApplicationListener<?> listener : this.application.getListeners()) {
if (listener instanceof ApplicationContextAware) {
((ApplicationContextAware) listener).setApplicationContext(context);
}
context.addApplicationListener(listener);
}
this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}
@Override
public void started(ConfigurableApplicationContext context) {
context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
}
@Override
public void running(ConfigurableApplicationContext context) {
context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
if (context != null && context.isActive()) {
// Listeners have been registered to the application context so we should
// use it at this point if we can
context.publishEvent(event);
}
else {
// An inactive context may not have a multicaster so we use our multicaster to
// call all of the context's listeners instead
if (context instanceof AbstractApplicationContext) {
for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
.getApplicationListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
this.initialMulticaster.multicastEvent(event);
}
}
private static class LoggingErrorHandler implements ErrorHandler {
//...
}
}
主要关注2点
contextLoaded() 这个回调方法比较特殊,后续会说到。
SpringApplicationRunListeners 运行监听器组
SpringApplicationRunListener 的复数形式,用于封装多个运行监听器,以便对这些运行监听器进行批量调用
class SpringApplicationRunListeners {
private final Log log;
private final List<SpringApplicationRunListener> listeners;
SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
this.log = log;
this.listeners = new ArrayList<>(listeners);
}
void starting() {
for (SpringApplicationRunListener listener : this.listeners) {
listener.starting();
}
}
void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}
void contextPrepared(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.contextPrepared(context);
}
}
void contextLoaded(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.contextLoaded(context);
}
}
void started(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.started(context);
}
}
void running(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.running(context);
}
}
void failed(ConfigurableApplicationContext context, Throwable exception) {
for (SpringApplicationRunListener listener : this.listeners) {
callFailedListener(listener, context, exception);
}
}
private void callFailedListener(SpringApplicationRunListener listener, ConfigurableApplicationContext context,
Throwable exception) {
//...
}
}
springboot中对应的启动过程
//调用 SpringApplication 本身的 getRunListeners() 方法
SpringApplicationRunListeners listeners = getRunListeners(args);
//执行运行监听器的starting()回调方法
listeners.starting();
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
//先根据spring.factories中的配置创建要使用的运行监听器实例,再把这些运行监听器实例封装为运行监听器组
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
springboot本身只给 SpringApplicationRunListener 提供了一个实现类,springboot自身的 spring.factories 中也只启用了这个实现
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
为什么还要打包为 SpringApplicationRunListeners 进行批量操作?就一个运行监听器,似乎没有打包的必要。
因为开发者可能需要在应用启动的特定阶段做一些自定义操作,要实现 SpringApplicationRunListener 重写回调方法,这样就会存在多个 SpringApplicationRunListener。
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
//获取或创建环境,有现成的环境就直接使用,没有则调用构造方法new一个
ConfigurableEnvironment environment = getOrCreateEnvironment();
//配置环境:上一步调用无参构造器创建了Environment实例,这一步对创建好的Environment实例进行配置
configureEnvironment(environment, applicationArguments.getSourceArgs());
//绑定配置属性源、环境
ConfigurationPropertySources.attach(environment);
//至此环境已配置好,触发 SpringApplicationRunListener 的 environmentPrepared() 回调方法
listeners.environmentPrepared(environment);
//将配置好的环境绑定到 SpringApplication 上
bindToSpringApplication(environment);
//如果不是自定义的环境,则根据情况确定是否需要转换环境
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
//重新绑定属性源、环境
ConfigurationPropertySources.attach(environment);
return environment;
}
getOrCreateEnvironment() 获取或创建环境
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();
}
}
configureEnvironment() 配置环境
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
// 处理 ConversionService 配置
// ConversionService 是spring的一个线程安全的类型转换接口,convert(Object, Class) 可以将对象转换为指定类型,关键在于是线程安全的
if (this.addConversionService) {
ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);
}
//配置属性源
configurePropertySources(environment, args);
//配置profiles属性:这一步会把 spring.profiles 激活的配置文件添加到环境中
configureProfiles(environment, args);
}
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
MutablePropertySources sources = environment.getPropertySources();
//配置默认属性
if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
}
//配置命令行属性:如果给main()方法传递了命令行参数,则添加到Environment实例中
if (this.addCommandLineProperties && args.length > 0) {
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));
}
}
}
Banner接口
@FunctionalInterface
public interface Banner {
/**
* Print the banner to the specified print stream
*
* @param environment the spring environment
* @param sourceClass the source class for the application
* @param out the output print stream
*/
void printBanner(Environment environment, Class<?> sourceClass, PrintStream out);
/**
* banner的打印模式
*/
enum Mode {
/**
* 禁用banner,不打印banner
*/
OFF,
/**
* 输出流使用 System.out,标准输出,输出到控制台
*/
CONSOLE,
/**
* 输出到日志文件中
*/
LOG
}
}
SpringApplicaition 的 printBanner() 方法
//banner的默认打印模式是输出到控制台
private Banner.Mode bannerMode = Banner.Mode.CONSOLE;
/**
* 根据打印模式打印 banner
*/
private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
: new DefaultResourceLoader(null);
//通过 SpringApplicationBannerPrinter 类打印banner
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
//console模式的打印
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
SpringApplicationBannerPrinter 类,用于打印banner
class SpringApplicationBannerPrinter {
//文本banner的文件位置对应的配置项
static final String BANNER_LOCATION_PROPERTY = "spring.banner.location";
//图片banner的文件位置对应的配置项
static final String BANNER_IMAGE_LOCATION_PROPERTY = "spring.banner.image.location";
//默认的banner文件加载位置
static final String DEFAULT_BANNER_LOCATION = "banner.txt";
//图片banner支持的文件扩展名
static final String[] IMAGE_EXTENSION = { "gif", "jpg", "png" };
/**
* 默认banner
*/
private static final Banner DEFAULT_BANNER = new SpringBootBanner();
private final ResourceLoader resourceLoader;
private final Banner fallbackBanner;
SpringApplicationBannerPrinter(ResourceLoader resourceLoader, Banner fallbackBanner) {
//...
}
Banner print(Environment environment, Class<?> sourceClass, Log logger) {
//...
}
Banner print(Environment environment, Class<?> sourceClass, PrintStream out) {
//...
}
private Banner getBanner(Environment environment) {
//...
}
/**
* 打印文本形式的banner
*/
private Banner getTextBanner(Environment environment) {
//...
}
/**
* 打印图片形式的banner
*/
private Banner getImageBanner(Environment environment) {
//...
}
/**
* 创建文本形式的banner字符串
*/
private String createStringFromBanner(Banner banner, Environment environment, Class<?> mainApplicationClass)
throws UnsupportedEncodingException {
//...
}
private static class Banners implements Banner {
//...
}
private static class PrintedBanner implements Banner {
//...
}
}
/**
* 要使用的应用上下文的类型
*/
private Class<? extends ConfigurableApplicationContext> applicationContextClass;
protected ConfigurableApplicationContext createApplicationContext() {
//确定要创建的应用上下文类型:如果没有指定应用上下文的类型,则根据推断得到的应用类型自动确定
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
}
}
//通过反射调用对应的构造方法创建应用上下文实例
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
上一步的创建应用上下文,只是通过反射调用构造方法创建实例,尚未对上下文实例进行配置,这一步用于配置、初始化创建好的上下文实例
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//设置环境
context.setEnvironment(environment);
//执行应用上下文的后置处理,主要是设置一些尚未填充的属性
postProcessApplicationContext(context);
//执行应用上下文的各个初始化器 ApplicationContextInitializer,对上下文进行初始化
// ApplicationContextInitializer 接口中只有一个 initialize() 方法,用于对应用上下文进行初始化,这个可当做扩展点做一些自定义的初始化操作
applyInitializers(context);
//至此上下文准备完毕,执行运行监听器的 contextPrepared() 回调方法
listeners.contextPrepared(context);
//如果允许打印启动信息,则
if (this.logStartupInfo) {
//打印应用正在启动的信息,示例:Starting DemoApplication on DESKTOP-E74978P with PID 49824
logStartupInfo(context.getParent() == null);
//打印 profiles 激活的配置,示例:The following profiles are active: test
//没有设置激活的配置则打印默认配置:No active profile set, falling back to default profiles: default
//spring.profiles除了include、active之外,还有一个default属性用于指定默认配置文件
logStartupProfileInfo(context);
}
//获取内置的低级容器,以对内置的低级容器进行一些配置
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//注册一些特殊的单例bean:应用参数、banner
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
//配置是否允许bean定义覆盖
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
//配置是否允许bean的懒加载。默认false,高级容器启动时就实例化所有单例
if (this.lazyInitialization) {
//如果允许bean的懒加载,则给内置的低级容器添加懒加载对应的后置处理器
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
//获取所有的资源,所有的资源指的是 this.primarySources、this.sources 2个属性指定的资源
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//加载所有的资源:使用 BeanDefinitionLoader 加载所有的bean定义,完成bean的注册
load(context, sources.toArray(new Object[0]));
//执行运行监听器的 contextLoaded() 回调方法
listeners.contextLoaded(context);
}
/**
* 执行应用上下文的后置处理,主要是设置一些尚未填充的属性
*/
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
if (this.beanNameGenerator != null) {
context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
this.beanNameGenerator);
}
if (this.resourceLoader != null) {
if (context instanceof GenericApplicationContext) {
((GenericApplicationContext) context).setResourceLoader(this.resourceLoader);
}
if (context instanceof DefaultResourceLoader) {
((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader());
}
}
if (this.addConversionService) {
context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance());
}
}
实质是调用高级容器的2个方法
refresh() 执行的具体过程可参考的我的另一篇博文
https://blog.csdn.net/chy_18883701161/article/details/120550053
2种Runner
runner有2种:CommandLineRunner、ApplicationRunner
@FunctionalInterface
public interface CommandLineRunner {
void run(String... args) throws Exception;
}
@FunctionalInterface
public interface ApplicationRunner {
void run(ApplicationArguments args) throws Exception;
}
都是函数式接口、只有一个run()方法,区别是参数类型不同
其实使用的都是 main() 方法的参数,ApplicationArguments 只是对main()参数的封装。
springboot启动过程中调用的方法
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
//从容器中获取runner
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
//对runner进行排序。就是说支持多个runner,可以用 @Order 或实现Order接口指定这些runner的执行顺序
AnnotationAwareOrderComparator.sort(runners);
//遍历执行这些runner的run()方法。注意传入的args是 ApplicationArguments 类型
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
// 对于 CommandLineRunner,会先 args.getSourceArgs() 获取 String... 类型的参数
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
man()参数可以包含 server.port 之类的应用预定义配置,也可以包含自定义的参数。通过2个runner接口,可以在应用启动后根据 main() 方法传入的参数做一些额外处理,当然,不使用main()参数、直接做一些与mian()参数无关的操作也行。
springboot在启动过程中主要预留了4个扩展点
1、上下文初始化接口 ApplicationContextInitializer
在 prepareContext() 准备上下文阶段,会调用 applyInitializers() 执行各个ApplicationContextInitializer接口,这个接口可做一些自定义的上下文初始化操作。
2、预留的 afterRefresh() 方法
在 refresh() 刷上下文后会调用这个方法,像 afterRefresh() 这种 protected 修饰的空方法,基本都是预留的扩展点,留给子类重写的,不想让子类重写的都是定义为 private。
不熟悉springboot的同学,使用 afterRefresh() 很容易踩坑,能用其它方式就用其它方式。
3、Runner 回调
在应用启动后会执行Runner回调,方法参数和 afterRefresh() 差不多,都是应用上下文 context、应用参数 applicationArguments。
4、自定义运行监听器 SpringApplicationRunListener
可以在启动过程中的多个阶段做一些额外处理。
有同学可能会疑惑
//new SpringApplication() 阶段会创建 ApplicationListener 实例
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//实例 run() 方法启动阶段,会创建事件广播器实例,把 ApplicationListener 实例添加到事件广播器上
SpringApplicationRunListeners listeners = getRunListeners(args);
// 调用高级容器的 refresh() 方法刷新上下文阶段
// initApplicationEventMulticaster() 会创建事件广播器实例
// registerListeners() 会把 ApplicationListener 实例添加到事件广播器上
refreshContext()
好像重复创建了事件广播器、重复添加了应用监听器,是否重复、矛盾?
首先明确2点
1、getSpringFactoriesInstances() 只负责创建 spring.factories 中指定的实现类的实例,这些实例是通过反射调用无参构造器直接创建的,没有放到容器中。
getSpringFactoriesInstances() 不会创建注解、xml配置文件、java代码配置方式配置的bean实例,这些方式配置的bean实例,由spring容器通过 beanFactory.getBean() 创建,会放到容器中。
比如应用监听器 ApplicationListener,getSpringFactoriesInstances() 创建spring.factories 中配置的应用监听器,spring容器创建
注解、xml配置文件、java代码配置方式配置的应用监听器,这才是要使用的全部的应用监听器。
2、应用监听器、事件广播器之间的添加|绑定,是实例之间的绑定,应用监听器实例只能接收到所绑定的事件广播器实例发布的事件。
this.initialMulticaster.addApplicationListener(listener);`
应用监听器实例 listenerA 绑定到事件广播器实例 multicasterA 上,没有绑定到事件广播器实例 multicasterB 上,能不能接收到 multicasterB 发布的事件?显然不能,你绑定的是 multicasterB,跟 multicasterA 有什么关系,multicasterA 都不认识你。
springboot中的相关源码
// new SpringApplication()创建SpringApplication实例阶段
// getSpringFactoriesInstances() 创建 spring.factories 中指定的 ApplicationListener 实现类的实例
// 保存在 SpringApplication实例 的成员变量中
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 实例 run() 方法启动应用阶段
// 直接 new 创建事件广播器实例,保存在运行监听器 EventPublishingRunListener 实例自身的成员变量中
// 将之前创建的spring.factories的 ApplicationListener 实例添加到这个事件广播器实例上
SpringApplicationRunListeners listeners = getRunListeners(args);
//执行以上2步时,上下文|容器尚未创建,不能通过 beanFactory.getBean() 来获取实例,不能保存到容器中
//都是通过new或反射调用构造方法来创建实例,以成员变量的形式保存
//创建、准备应用上下文
createApplicationContext()
prepareContext()
//至此,上下文已完成创建、配置,所有bean定义已加载
//调用高级容器的 refresh() 方法刷新上下文,创建所有非懒加载的单例bean的实例
refreshContext()
在 prepareContext() 准备上下文阶段,会调用运行监听器的 contextLoaded() 回调方法
/**
* 其它回调方法都是直接发布事件,这个回调方法除了发布事件,
* 还会把之前创建的、来自 spring.factories 的 ApplicationListener 实例,传给创建好的上下文 ApplicationContext
*/
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
//遍历之前创建的来自 spring.factories 的 ApplicationListener 实例
for (ApplicationListener<?> listener : this.application.getListeners()) {
if (listener instanceof ApplicationContextAware) {
((ApplicationContextAware) listener).setApplicationContext(context);
}
//传给 ApplicationContext
context.addApplicationListener(listener);
}
//发布应用已准备好事件 ApplicationPreparedEvent
this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}
高级容器 refresh() 刷新阶段
AbstractApplicationContext 的 refresh()方法
//准备刷新
prepareRefresh();
//初始化事件广播器
initApplicationEventMulticaster();
//注册应用监听器 ApplicationListener
registerListeners();
AbstractApplicationContext 中的相关成员变量
/**
* 内置的事件广播器
*/
@Nullable
private ApplicationEventMulticaster applicationEventMulticaster;
/**
* 这个成员变量用于存储所有非spring容器方式创建的 ApplicationListener 实例(通常是 spring.factories 方式创建的)
* 包括spring自身创建的、其它来源的
*
*
* AbstractApplicationContext 给这个成员提供了2个public方法对外暴露使用:
* addApplicationListener() 添加单个监听器、 getApplicationListeners() 获取这个监听器集合
*
* final 修饰引用类型,只是不能修改引用本身,可以修改引用指向的对象
* 下面2个集合因为会初始化、重置为null,引用会变化,没有用final修饰
*/
private final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet<>();
/**
* 存储早期要使用的应用监听器
*
* private,没有提供对外的方法暴露出去,只在内部使用
*/
@Nullable
private Set<ApplicationListener<?>> earlyApplicationListeners;
/**
* 存储早期事件(在事件广播器创建之前触发的事件)
*/
@Nullable
private Set<ApplicationEvent> earlyApplicationEvents;
prepareRefresh() 准备刷新阶段
if (this.earlyApplicationListeners == null) {
this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
}
else {
//如果 earlyApplicationListeners 不是空的,则添加到 applicationListeners 中,后续会以 applicationListeners 进行绑定
this.applicationListeners.clear();
this.applicationListeners.addAll(this.earlyApplicationListeners);
}
//初始化存储早期事件的集合
this.earlyApplicationEvents = new LinkedHashSet<>();
initApplicationEventMulticaster() 初始化事件广播器阶段
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
//如果容器中有事件广播器的bean定义
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
//则 beanFactory.getBean() 创建实例放到容器中,并赋给 AbstractApplicationContext 的成员变量保存
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
if (logger.isTraceEnabled()) {
logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
}
}
else {
//否则直接new一个事件广播器实例,放到容器中,并赋给成员变量
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
if (logger.isTraceEnabled()) {
logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
}
}
}
总之就是创建事件广播器的实例,放到容器中,并赋给对应的成员变量。
registerListeners() 注册监听器阶段
protected void registerListeners() {
//添加成员变量形式存储的、所有 spring.factories 方式创建的 ApplicationListener 实例
for (ApplicationListener<?> listener : getApplicationListeners()) {
getApplicationEventMulticaster().addApplicationListener(listener);
}
//添加spring容器中所有的 ApplicationListener 类型的bean
//这里是以beanName的形式进行添加,添加的是bean定义中的beanName,对应的监听器实例此时还没有实例化,会在后续步骤进行实例化
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
for (String listenerBeanName : listenerBeanNames) {
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
}
//广播早期集合中存储的事件,早期事件只广播给 spring.factories 中定义的早期监听器
//此时事件广播器已经创建,这些spring.factories中的事件监听器都添加到了事件广播器上,可以广播早期事件了
Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
this.earlyApplicationEvents = null;
if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
getApplicationEventMulticaster().multicastEvent(earlyEvent);
}
}
}
添加了 spring.factories 方式创建的应用监听器、容器中的应用监听器,这才完成了要使用的全部监听器的注册。
总结
虽然整个启动过程创建了2个事件广播器实例,发生了多次应用监听器的绑定,但作用对象、作用范围各不相同,并不重复、矛盾。
事件广播器常见的使用方式
AbstractApplicationContext 自身提供了发布事件的方法
//如果处于早期阶段,尚未创建事件广播器、注册应用监听器,事件不会直接发布,而是暂存在早期事件集合中
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
}
else {
//实质是调用内置的事件广播器的方法广播|发布事件
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
AbstractApplicationContext 以成员变量形式内置的事件广播器、放到容器中的事件广播器,其实是同一个事件广播器实例,使用容器中的事件广播器实例或者AbstractApplicationContext实例发布事件,本质都是一样的。
//注入 ApplicationContext 实例。创建的高级容器实例本身也会作为bean实例放到容器中。
//高级容器可以理解为 DefaultListableBeanFactory类中定义的储存 BeanDefinition、beanName、bean实例的一系列集合,使用的高级容器的实现类本身也是一个bean,创建后也会添加、注册上去
@Autowired
private ApplicationContext applicationContext;
//注入容器中的事件广播器实例,类型是 ApplicationEventMulticaster,beanName是对应的camel写法
@Autowired
private ApplicationEventMulticaster applicationEventMulticaster;
public void publish() {
//事件,参数指定事件源
MyApplicationEvent myApplicationEvent = new MyApplicationEvent(this);
//通过容器中的事件广播器实例发布事件
applicationEventMulticaster.multicastEvent(myApplicationEvent);
//通过 AbstractApplicationContext 实例发布事件
applicationContext.publishEvent(myApplicationEvent);
}
作为事件广播器来说,ApplicationContext 是高级容器,包含了太多东西,偏重量级,ApplicationEventMulticaster 比较纯粹,更推荐用 ApplicationEventMulticaster。
我在自定义运行监听器的各个回调方法中打印出了回调方法名称,并标注了对应的启动阶段、触发的回调方法、回调方法中发布的事件
"C:\Program Files\Java\jdk1.8.0_281\bin\java.exe" ...
# listeners.starting()中,starting() ,发布应用启动事件 ApplicationStartingEvent
starting...
# prepareEnvironment() 准备环境,environmentPrepared() ,发布环境已准备事件 ApplicationEnvironmentPreparedEvent
environmentPrepared...
# printBanner() 打印banner。打印的默认banner中会包含springboot的版本
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.12.RELEASE)
# prepareContext() 准备上下文,contextPrepared() ,发布上下文已初始化事件 ApplicationContextInitializedEvent
contextPrepared...
# prepareContext()打印的应用启动信息
2021-10-15 15:20:08.098 INFO 50880 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication on DESKTOP-E74978P with PID 50880 (C:\Users\chy\Desktop\demo\target\classes started by chy in C:\Users\chy\Desktop\demo)
# prepareContext()打印的profile激活的配置
2021-10-15 15:20:08.109 INFO 50880 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to default profiles: default
# refreshContext() 刷新上下文,contextLoaded() ,发布应用已准备好事件 ApplicationPreparedEvent
contextLoaded...
#初始化内置tmocat要使用的端口
2021-10-15 15:20:10.434 INFO 50880 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
#开始启动内置tomcat
2021-10-15 15:20:10.453 INFO 50880 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
#开启tomcat的servlet引擎,打印信息中包含了内置tomcat的版本
2021-10-15 15:20:10.453 INFO 50880 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.46]
#初始化tomcat本身的应用上下文,打印信息中包含了映射的域名、应用映射的根路径
2021-10-15 15:20:10.604 INFO 50880 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/mall] : Initializing Spring embedded WebApplicationContext
#tomcat本身的根上下文初始化完成,打印花费的时间
2021-10-15 15:20:10.605 INFO 50880 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2327 ms
#初始化tomcat的执行器
2021-10-15 15:20:11.084 INFO 50880 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
#内置tomcat启动完成,打印tomcat使用的端口、应用映射的根路径
2021-10-15 15:20:11.643 INFO 50880 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '/mall'
#至此,应用启动完成,打印启动应用总共花费的时间
2021-10-15 15:20:11.678 INFO 50880 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 4.559 seconds (JVM running for 5.832)
# listeners.started() 执行运行监听器组的 started() 方法,started(),发布应用已启动事件 ApplicationStartedEvent
started...
# callRunners() 执行Runner 回调
ApplicationRunner...
CommandLineRunner...
# listeners.running() 执行运行监听器组的 running() 方法,running(),发布应用就绪事件 ApplicationReadyEvent
running...
#初始化springmvc的核心组件 DispatcherServlet,会作为bean放到容器中,已经打印出了bean的名称 'dispatcherServlet'
2021-10-15 15:20:12.917 INFO 50880 --- [(2)-10.10.10.84] o.a.c.c.C.[Tomcat].[localhost].[/mall] : Initializing Spring DispatcherServlet 'dispatcherServlet'
#初始化DispatcherServlet这个servlet,把DispatcherServlet配置为servlet
2021-10-15 15:20:12.917 INFO 50880 --- [(2)-10.10.10.84] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
#DispatcherServlet初始化完毕,打印初始化DispatcherServlet花费的时间
2021-10-15 15:20:12.929 INFO 50880 --- [(2)-10.10.10.84] o.s.web.servlet.DispatcherServlet : Completed initialization in 12 ms
可以看到,在应用启动完成后会初始化 springmvc | spring webflux 的核心组件。
如果要查看更详细的启动信息,可以将 debug 设置为 true,或者将日志级别设置为 debug。
都是准备好环境、材料,刷一下,想要的东西、效果就有了,现在是不是觉得 refresh 刷新 这个方法名很形象?spring就是这么神奇,刷一下就有了。
bean 豆子 => bean定义
spring 春天 => 春回大地,一场春雨(refresh方法)过后,刷新大地,万物复苏,豆子萌芽茁壮成长(bean实例化)
springboot => boot 引导、启动,可以看做spring的脚手架,用于快速搭建spring项目