有道无术,术尚可求,有术无道,止于术。
本系列Spring Boot版本2.7.0
在使用Spring Boot
开发应用时,我们只需要一个简单启动类,就能完成整个应用的加载启动,这个启动流程实际是很复杂,接下来我们就分几个篇章深入了解一下。
首先本篇会着重介绍SpringApplication
类。
SpringApplication
翻译过来就是Spring 应用程序的意思,它的主要作用就是在JAVA main 方法中引导和启动Spring应用程序。
在源码中属于spring-boot
模块:
SpringApplication
的成员属性描述如下:
// banner图的默认位置 banner.txt
public static final String BANNER_LOCATION_PROPERTY_VALUE = SpringApplicationBannerPrinter.DEFAULT_BANNER_LOCATION;
// 配置banner 位置的属性名称: spring.banner.location
public static final String BANNER_LOCATION_PROPERTY = SpringApplicationBannerPrinter.BANNER_LOCATION_PROPERTY;
// 设置java.awt.headless模式 的属性名
private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
// 日志记录对象
private static final Log logger = LogFactory.getLog(SpringApplication.class);
// 关闭Spring Boot 应用程序钩子
static final SpringApplicationShutdownHook shutdownHook = new SpringApplicationShutdownHook();
// 主类
private Set<Class<?>> primarySources;
// 创建 ApplicationContext 的其他资源,可以是:类名、包名或 XML 资源位置
private Set<String> sources = new LinkedHashSet<>();
// 主类的Class 类型
private Class<?> mainApplicationClass;
// Banner 图的模式,可以关闭开启
private Banner.Mode bannerMode = Banner.Mode.CONSOLE;
// 是否 打印 Spring Boot 启动的时长日志
private boolean logStartupInfo = true;
// 是否通过命令行参数向application.properties中添加属性配置,比如 将java -jar 中设置的参数传递到 application.properties重
private boolean addCommandLineProperties = true;
// 是否添加类型转换服务
private boolean addConversionService = true;
// Banner 对象
private Banner banner;
// 资源加载器
private ResourceLoader resourceLoader;
// Bean名称生成器
private BeanNameGenerator beanNameGenerator;
// 配置属性环境
private ConfigurableEnvironment environment;
// Web 程序的类型,SERVLET、REACTIVE
private WebApplicationType webApplicationType;
// 设置headless 模式(告诉系统该程序没有显示器、鼠标能外设)
this.headless = true;
// 是否注册关闭钩子
private boolean registerShutdownHook = true;
// 设置ApplicationContextInitializer(可用在初始化的时候处理一些东西)
private List<ApplicationContextInitializer<?>> initializers;
// 监听器
private List<ApplicationListener<?>> listeners;
// 默认属性
private Map<String, Object> defaultProperties;
// 使用 {@link BootstrapRegistry} 之前对其进行初始化的回调接口
private List<BootstrapRegistryInitializer> bootstrapRegistryInitializers;
// 附加配置文件
private Set<String> additionalProfiles = Collections.emptySet();
// 允许 Bean 覆盖
private boolean allowBeanDefinitionOverriding;
// 允许循环引用
private boolean allowCircularReferences;
// 是否自定义环境
private boolean isCustomEnvironment = false;
// 是否延迟初始化
private boolean lazyInitialization = false;
// 环境前缀
private String environmentPrefix;
// 应用上下文工厂,默认ConfigurableApplicationContext
private ApplicationContextFactory applicationContextFactory = ApplicationContextFactory.DEFAULT;
// 程序启动器
private ApplicationStartup applicationStartup = ApplicationStartup.DEFAULT;
在这些属性中,可以看到很多和环境、初始化、IOC容器、Banner、日志、钩子有关的配置。
SpringApplication
的构造函数,会初始化很多属性,我们在启动时,也可以自己创建对象并设置这些属性,实现自定义启动。
// 参数
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 资源集合
this.sources = new LinkedHashSet();
// banner 图模式,可以设置在控制台、关闭、日志文件中显示
this.bannerMode = Mode.CONSOLE;
// 打印 Spring Boot 启动的时长日志
this.logStartupInfo = true;
// 外部命令
this.addCommandLineProperties = true;
// 对象间的转换的转换器
this.addConversionService = true;
// 设置headless 模式(告诉系统该程序没有显示器、鼠标能外设)
this.headless = true;
// 是否注册关闭钩子
this.registerShutdownHook = true;
// 配置额外的配置文件
this.additionalProfiles = Collections.emptySet();
// 是否自定义环境
this.isCustomEnvironment = false;
// 是否延迟初始化
this.lazyInitialization = false;
// 使用默认的IOC容器
this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
// 检测应用程序启动阶段,监控启动时的各种性能指标
this.applicationStartup = ApplicationStartup.DEFAULT;
// 资源加载时,这里为null
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 启动类,也就是主方法传去的参数
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
// Web程序类型, NONE,SERVLET(默认), REACTIVE;
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 初始化bootstrapRegistryInitializers引导程序注册的初始化器
// 将spring.factories中key为BootstrapRegistryInitializer对应的类型实例化后放到成员变量initializers里(
this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// 将spring.factories中key为ApplicationContextInitializer对应的类型实例化后放到成员变量initializers里(调用默认构造函数,生成一个实例)
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 将spring.factories中key为ApplicationListener对应的类型实例化后放到成员变量Listeners里(调用默认构造函数,生成一个实例)
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
// 推断出当前启动的main函数所在的类。
this.mainApplicationClass = this.deduceMainApplicationClass();
}
可以在Debug 中,看到改对象创建后加载的一系列属性,创建完成后就开始调用run
方法启动程序了。
在大多数情况下,直接在main 方法中调用run
方法启动应用程序,run
方法会运行 Spring 应用程序,创建并刷新一个新的IOC 容器。
@SpringBootApplication
public class App1 {
public static void main(String[] args) {
SpringApplication.run(App1.class,args);
}
}
也可以先创建SpringApplication
实例,然后设置属性,再调用run
方法启动,以实现更高级的配置。
@SpringBootApplication
public class App1 {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MyApplication.class);
// ... customize application settings here
application.run(args)
}
}
程序启动都是通过SpringApplication.run
方法作为入口的,run
方法会new
一个SpringApplication
,然后再调用对象的run
方法。
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
接着调用到run(String... args)
方法,Spring Boot
在这里完成整个应用的加载、创建、运行,最后会返回一个IOC容器,具体的每一步流程后面介绍。
public ConfigurableApplicationContext run(String... args) {
// 开始时间
long startTime = System.nanoTime();
// 1. 创建Bootstrap上下文
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
ConfigurableApplicationContext context = null;
// 2. 设置系统参数 java.awt.headless
this.configureHeadlessProperty();
// 3. 获取监听器,每个监听器都开启一个独立的线程去执行监听
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 4. 封装命令行参数,并准备环境
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
// 5. 查找并设置配置文件信息
this.configureIgnoreBeanInfo(environment);
// 6. 打印 Banner
Banner printedBanner = this.printBanner(environment);
// 7. 创建应用上下文,并添加一个应用程序监视器
context = this.createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
// 8. 准备应用上下文
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 9. 刷新应用上下文
this.refreshContext(context);
// 10.刷新完成后调
this.afterRefresh(context, applicationArguments);
// 11. 计算启动时间,JDK Duration类表示秒或纳秒时间间隔,
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
// 12. 打印启动日志
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);
}
// 13. 开始执行监听器
listeners.started(context, timeTakenToStartup);
// 14. 从应用上下文中获取ApplicationRunner和CommandLinerRunner类型的Bean并调用他们的run方法
this.callRunners(context, applicationArguments);
} catch (Throwable var12) {
// 处理异常
this.handleRunFailure(context, var12, listeners);
throw new IllegalStateException(var12);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
// 15. 准备监听器
listeners.ready(context, timeTakenToReady);
// 返回上下文
return context;
} catch (Throwable var11) {
this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var11);
}
}
SpringApplication
中,有1000多行代码,提供了很多私有方法,具体怎么执行的,后面一步步分析。