1、 直接开干
spring boot 项目主类启动入口
@SpringBootApplication //<1>
public class SrcSpringBootExampleTsshareApplication {
public static void main(String[] args) {
//<2>
SpringApplication.run(SrcSpringBootExampleTsshareApplication.class, args);
}
}
- <1> @SpringBootApplication 为Spring Boot 项目启动类注解,标明是 Spring Boot 应用。通过它,可以开启自动配置的功能;
- <2> SpringApplication#run(Class<?> primarySource, String… args) 方法,启动Spring Boot 应用,Spring Boot 项目的核心处理都在这个方法里面处理,下面重点分析这个方法。
2、SpringApplication
//<1>
public static void main(String[] args) throws Exception {
SpringApplication.run(new Class<?>[0], args);
}
//<2>
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
//<3>
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
//<4>
public static int exit(ApplicationContext context, ExitCodeGenerator... exitCodeGenerators) {
Assert.notNull(context, "Context must not be null");
int exitCode = 0;
try {
try {
ExitCodeGenerators generators = new ExitCodeGenerators();
Collection<ExitCodeGenerator> beans = context.getBeansOfType(ExitCodeGenerator.class).values();
generators.addAll(exitCodeGenerators);
generators.addAll(beans);
exitCode = generators.getExitCode();
if (exitCode != 0) {
context.publishEvent(new ExitCodeEvent(context, exitCode));
}
}
finally {
close(context);
}
}
catch (Exception ex) {
ex.printStackTrace();
exitCode = (exitCode != 0) ? exitCode : 1;
}
return exitCode;
}
- <1><2>最终调用第<3>个 ,提供Spring Boot 的运行入口
- <4> 提供Spring Boot 关闭功能的一些退出动作
2.1 构造方法
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources (see {@link SpringApplication class-level}
* documentation for details. The instance can be customized before calling
* {@link #run(String...)}.
* @param resourceLoader the resource loader to use
* @param primarySources the primary bean sources
* @see #run(Class, String[])
* @see #setSources(Set)
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
//资源加载器
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//通过 classpath ,判断 Web 应用类型。
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
- 根据构造方法来看该类的部分属性
- resourceLoader :属性,资源加载器。有个大佬的文章可以参考看看【死磕 Spring】—— IoC 之 Spring 统一资源加载策略
- primarySources :主要的 Java Config 类的数组,就是SrcSpringBootExampleTsshareApplication 类
- webApplicationType :通过 classpath ,判断 Web 应用类型。REACTIVE(响应式web ,spring webflux),SERVLET(spring mvc),NONE(非web)
- initializers:getSpringFactoriesInstances(ApplicationContextInitializer.class));调用SpringFactoriesLoader 加载在
META-INF/spring.factories
下ApplicationContextInitializer 初始化实例 - listeners:setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
调用SpringFactoriesLoader 加载在META-INF/spring.factories
下ApplicationListener 初始化实例 - mainApplicationClass:,调用 #deduceMainApplicationClass() 方法,获得是调用了哪个 #main(String[] args) 方法;这个 mainApplicationClass 属性,没有什么逻辑上的用途,主要就是用来打印下日志,说明是通过这个类启动 Spring 应用的。
2.1.1 getSpringFactoriesInstances
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
// <1> 排序对象们加载指定类型对应的,在 `META-INF/spring.factories` 里的类名的数组
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// <2>创建对象们
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// <3>创建对象们
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
- <1> #SpringFactoriesLoader.loadFactoryNames(type, classLoader)
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/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 {
// 通过类加载器获取资源 "META-INF/spring.factories" 下对应的map 资源
Enumeration<URL> urls = (classLoader != null ?
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);
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);
}
}
- <2> createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);根据第一步获取的结果进行创建对象实例
- <3> 对实例进行排序,排序规则按照注解 @Order(num)
2.2 run方法
#run(String.. arg) 运行 Spring 应用。代码如下:
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
* > 1. 配置属性
* > 2。设置系统属性『java.awt.headless』
* > 3. 获取监听器,发布应用开始启动事件
* > 4. 初始化输入参数
* > 5. 配置环境,
* > 6. 输出 banner
* > 7. 创建上下文
* > 8. 实例化异常分析器
* > 9. 预处理上下文
* > 10. 刷新上下文
* > 11. 再一次刷新上下文
* > 12. 停止 StopWatch 统计时长
* > 13. 打印 Spring Boot 启动的时长日志
* > 14. 发布应用已经启动的事件
* > 15. 遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法
* > 16. 通知 SpringApplicationRunListener 的数组,Spring 容器运行中
*/
public ConfigurableApplicationContext run(String... args) {
//<1> 创建 StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
/**<2> 设置系统属性『java.awt.headless』,为true则启用headless模式支持**/
configureHeadlessProperty();
/***<3>
* 通过SpringFactoriesLoader 加载 *META-INF/spring.factories
* 找到声明的所有SpringApplicationRunListener的实现类并将其实例化,
* /之后逐个调用其started()方法,广播SpringBoot要开始执行了
*/
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
//<4>初始化参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//<5>创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile),
//并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法,广播Environment准备完毕。
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
//<6>打印banner
Banner printedBanner = printBanner(environment);
//<7>创建应用上下文 spring 容器
context = createApplicationContext();
//<8> 通过*SpringFactoriesLoader*检索*META-INF/spring.factories*,获取并实例化异常分析器
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//<9> 为ApplicationContext加载environment,之后逐个执行ApplicationContextInitializer的initialize()方法来进一步封装ApplicationContext,
//并调用所有的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一个空的contextPrepared()方法】,
//之后初始化IoC容器,并调用SpringApplicationRunListener的contextLoaded()方法,广播ApplicationContext的IoC加载完成,
//这里就包括通过**@EnableAutoConfiguration**导入的各种自动配置类。
//主要是调用所有初始化类的 initialize 方法
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//<10> 刷新上下文 启动spring 容器
refreshContext(context);
//<11> 再一次刷新上下文( 执行 Spring 容器的初始化的后置逻辑。默认实现为空) ,其实是空方法,可能是为了后续扩展。 。
afterRefresh(context, applicationArguments);
//<12>停止 StopWatch 统计时长
stopWatch.stop();
//<13> 打印 Spring Boot 启动的时长日志
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
//<14> 发布应用已经启动的事件 通知 SpringApplicationRunListener 的数组,Spring 容器启动完成
listeners.started(context);
//<15> 遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法。
//我们可以实现自己的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进行扩展。
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
//<16>通知 SpringApplicationRunListener 的数组,Spring 容器运行中
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
StopWatch 对象,并启动。StopWatch 主要用于简单统计 run 启动过程的时长。
- <2> 设置系统属性『java.awt.headless』,为true则启用headless模式支持
- <3> 获取监听器,发布应用开始启动事件,依然根据 2.1.1 中方式对SpringFactoriesLoader 加载 *META-INF/spring.factories
找到声明的所有SpringApplicationRunListener的实现类并将其实例化,之后逐个调用其started()方法,广播SpringBoot要开始执行了 - <4> 初始化参数 ApplicationArguments 对象
- <5> 加载属性配置,创建并配置当前SpringBoot应用将要使用的Environment(包括配置要使用的PropertySource以及Profile)
并遍历调用所有的SpringApplicationRunListener的environmentPrepared()方法,广播Environment准备完毕 - <6> #printBanner(environment);打印banner;
- <7> #createApplicationContext(); 创建应用上下文,也就是创建 Spring 容器;
- 根据初始化SpringApplication是类路径中初始化的webApplicationType来选择对应的容器,这里我们是SERVLET,他会通过Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);防区是初始化一个需要的SERVLET容器
- <8> #getSpringFactoriesInstances 方式获取并创建SpringBootExceptionReporter 异常报告器实例集合,记录启动过程中的异常信息。以下是exceptionReporters 的实体
包括18个FailureAnalyzer 错误分析器 - <9> #prepareContext(context, environment, listeners, applicationArguments, printedBanner); 预处理上下文,主要是调用所有初始化类的 #initialize(…) 方法 ,因为方法内容较多,详细见 【2.2.1 】,下面
- <10> #refreshContext(context); 刷新上下文 启动spring 容器 详细见 【2.2.2】,下面
- <11> #afterRefresh(context, applicationArguments); 再一次刷新应用上下文,表示上下文被刷新后调用,这是一个空方法,主要是为做一些上下刷新后的拓展备用;
- <12> 停止 StopWatch 统计时长;
- <13> 打印 Spring Boot 启动的时长日志;
- <14> #listeners.started(context); 发布应用已经启动的事件 通知 SpringApplicationRunListener 的数组,Spring 容器启动完成
- <15> #callRunners(context, applicationArguments); 遍历所有注册的ApplicationRunner和CommandLineRunner,并执行其run()方法;这一步我们可以实现自己的ApplicationRunner或者CommandLineRunner,来对SpringBoot的启动过程进行扩展。详细见【2.2.3】;
- <16> listeners.running(context); 循环调用SpringApplicationRunListener通知 SpringApplicationRunListener 的数组,Spring 容器运行中,整体的run 方法结束,其他是些run 过程中的异常处理代码,如果发生异常,则调用 #handleRunFailure(…) 方法,交给 SpringBootExceptionReporter 进行处理,并抛出 IllegalStateException 异常。
以下是针对上面run 方法中一些复杂方法的单独拆分分析
2.2.1 #prepareContext(context, environment, listeners, applicationArguments, printedBanner); 预处理上下文
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//<1> 设置context的environment 属性
context.setEnvironment(environment);
//<2> 设置context 一些属性
postProcessApplicationContext(context);
//<3> 初始化 ApplicationContextInitializer
applyInitializers(context);
//<4> 告诉SpringApplicationRunListener 监听器上下文初始化完毕
listeners.contextPrepared(context);
// <5> 打印日志
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
// <6> 设置 beanFactory 的属性
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
// Load the sources
// <7> 加载 BeanDefinition 们
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
// <8> 通知 SpringApplicationRunListener 的数组,Spring 容器加载完成。
listeners.contextLoaded(context);
}
- 这个方法代码相对比较多,主要是给上下文 context 做些初始化的赋值操作,为后面启动做准备
- <1> 设置 context 的 environment 属性
- <2> 调用 #postProcessApplicationContext(ConfigurableApplicationContext context) 方法,设置 context 的一些属性
- <3> 调用 #applyInitializers(ConfigurableApplicationContext context) 方法,初始化 ApplicationContextInitializer ,遍历 ApplicationContextInitializer 数组,逐个调用 ApplicationContextInitializer#initialize(context) 方法,进行初始化。
- <4> #listeners.contextPrepared(context); 告诉SpringApplicationRunListener 监听器上下文初始化完毕
- <5> 打印日志
- <6> 设置 beanFactory 的属性, 包括 应用参数 applicationArguments,打印的banner printedBanner,还有
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
根据懒加载的属性来设置懒加载的beanFactoryPostProcessor配置
- <7> #load(ApplicationContext context, Object[] sources) 方法,加载 BeanDefinition 们
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug("Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
// <1> 创建 BeanDefinitionLoader 对象
BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
// <2> 设置 loader 的属性
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
// <3> 执行 BeanDefinition 加载
loader.load();
}
- <1> 处,调用 #getBeanDefinitionRegistry(ApplicationContext context) 方法,创建 BeanDefinitionRegistry 对象
关于 BeanDefinitionRegistry 类,暂时不需要深入了解。感兴趣的胖友,可以看看 《【死磕 Spring】—— IoC 之 BeanDefinition 注册表:BeanDefinitionRegistry》 文章。
- <2> 设置 loader 的属性
- <3> 执行 BeanDefinition 加载 ,具体文章见 芋艿大佬的文章《【死磕 Spring】—— IoC 之加载 BeanDefinition》
*<8> #contextLoaded(ConfigurableApplicationContext context) 方法,通知 SpringApplicationRunListener 的数组,Spring 容器加载完成
2.2.2 refreshContext(context); 刷新上下文,启动spring 容器
private void refreshContext(ConfigurableApplicationContext context) {
//<1> 开启(刷新)Spring 容器
refresh(context);
//<2> 注册ShutdownHook 钩子
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
- <1> 调用 AbstractApplicationContext# refresh(context);
这个方法为启动容器最为核心的方法,处理的内容比较多,这里也会触发可以触发 Spring Boot 的自动配置的功能,包括tomcat的实例的创建和启动等 下面分析回着重细致讲解。 - <2> ConfigurableApplicationContext#registerShutdownHook() 方法,注册 ShutdownHook 钩子。这个钩子,主要用于 Spring 应用的关闭时,销毁相应的 Bean 们
2.2.3 callRunners(context, applicationArguments);
private void callRunners(ApplicationContext context, ApplicationArguments args) {
// <1> 获得所有 Runner 们 ApplicationRunner & CommandLineRunner
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
// <2> 排序 runners
AnnotationAwareOrderComparator.sort(runners);
// <3> 遍历 Runner 数组,调用执行逻辑
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
- <1> 获得所有 Runner 们 ApplicationRunner & CommandLineRunner;
- <2> 排序 runners;
- <3> 遍历 Runner 数组,调用执行逻辑
关于 Runner 功能的使用,可以找点文档自己看看。