一、SpringBoot启动的入口
1.当我们启动一个SpringBoot项目的时候,入口程序就是main方法,而在main方法中就执行了一个run方法。
@SpringBootApplication
public class StartApp {public static void main(String[] args) {// testSpringApplication.run(StartApp.class);}
}
2.SpringApplication.run方法
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {// 调用重载的run方法,将传递的Class对象封装为了一个数组return run(new Class<?>[] { primarySource }, args);}
静态帮助器,可用于使用默认设置和用户提供的参数从指定的源运行{@link SpringApplication}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {// 创建了一个SpringApplication对象,并调用其run方法// 1.先看下构造方法中的逻辑// 2.然后再看run方法的逻辑return new SpringApplication(primarySources).run(args);}
3.SpringApplication构造器
创建一个新的{@link SpringApplication}实例。应用程序上下文将从指定的主源加载bean(参见{@link SpringApplication class-level}文档了解详细信息)。可以在调用之前自定义实例public SpringApplication(Class<?>... primarySources) {// 调用其他的构造方法this(null, primarySources);}
this(null, primarySources);方法进入:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {// 传递的resourceLoader为nullthis.resourceLoader = resourceLoader;Assert.notNull(primarySources, "PrimarySources must not be null");// 记录主方法的配置类名称this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));// 记录当前项目的类型this.webApplicationType = WebApplicationType.deduceFromClasspath();// 加载配置在spring.factories文件中的ApplicationContextInitializer对应的类型并实例化// 并将加载的数据存储在了 initializers 成员变量中。setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));// 初始化监听器 并将加载的监听器实例对象存储在了listeners成员变量中setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));// 反推main方法所在的Class对象 并记录在了mainApplicationClass对象中this.mainApplicationClass = deduceMainApplicationClass();}
在本方法中完成了几个核心操作
- 推断当前项目的类型 this.webApplicationType是SERVLET;
- 加载配置在spring.factories文件中的ApplicationContextInitializer中的类型并实例化后存储在了initializers中。
ApplicationContextInitializer
getSpringFactoriesInstances(XXX.class)方法的作用是加载spring.factories文件中的kv对,后续获取XXX的对象从map中取不用在加载文件了。 - 和2的步骤差不多,完成监听器的初始化操作,并将实例化的监听器对象存储在了listeners成员变量中
- 通过StackTrace反推main方法所在的Class对象
4.SpringApplication的run方法:
public ConfigurableApplicationContext run(String... args) {// 创建一个任务执行观察器StopWatch stopWatch = new StopWatch();// 开始执行记录执行时间stopWatch.start();// 声明 ConfigurableApplicationContext 对象ConfigurableApplicationContext context = null;// 声明集合容器用来存储 SpringBootExceptionReporter 启动错误的回调接口Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();// 设置了一个名为java.awt.headless的系统属性// 其实是想设置该应用程序,即使没有检测到显示器,也允许其启动.//对于服务器来说,是不需要显示器的,所以要这样设置.configureHeadlessProperty();// 获取 SpringApplicationRunListener 加载的是 EventPublishingRunListener// 获取启动时的监听器---》 事件发布器 发布相关事件的 11个监听器 谁去发布事件?SpringApplicationRunListeners listeners = getRunListeners(args);// 触发启动事件 发布 starting 事件 --》 那么监听starting事件的监听器就会触发listeners.starting();try {// 构造一个应用程序的参数持有类ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 创建并配置环境ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);// 配置需要忽略的BeanInfo信息configureIgnoreBeanInfo(environment);// 输出的Banner信息Banner printedBanner = printBanner(environment);// 创建应用上下文对象context = createApplicationContext();// 加载配置的启动异常处理器exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);// 刷新前操作prepareContext(context, environment, listeners, applicationArguments, printedBanner);// 刷新应用上下文 完成Spring容器的初始化refreshContext(context);// 刷新后操作afterRefresh(context, applicationArguments);// 结束记录启动时间stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}// 事件广播 启动完成了listeners.started(context);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);}// 返回上下文对象--> Spring容器对象return context;}
在这个方法中完成了SpringBoot项目启动的很多核心的操作,总结下上面的步骤
- 创建了一个任务执行的观察器,统计启动的时间
- 声明ConfigurableApplicationContext对象
- 声明集合容器来存储SpringBootExceptionReporter即启动错误的回调接口
- 设置java.awt.headless的系统属性
- 获取我们之间初始化的监听器(EventPublishingRunListener),并触发starting事件
- 创建ApplicationArguments这是一个应用程序的参数持有类
- 创建ConfigurableEnvironment这时一个配置环境的对象
- 配置需要忽略的BeanInfo信息
- 配置Banner信息对象
- 创建对象的上下文对象
- 加载配置的启动异常的回调异常处理器
- 刷新应用上下文,本质就是完成Spring容器的初始化操作
- 启动结束记录启动耗时
- 完成对应的事件广播
- 返回应用上下文对象。
二、SpringApplication的run方法详解
对上面的方法进行解析:
1.创建了一个任务执行的观察器,统计启动的时间
StopWatch调用构造方法进行实例化,以及调用start方法:
启动一个命名任务。如果在不首先调用此方法的情况下调用stop()或计时方法,则结果是未定义的。参数:taskName—要启动的任务的名称public void start(String taskName) throws IllegalStateException {if (this.currentTaskName != null) {throw new IllegalStateException("Can't start StopWatch: it's already running");}this.currentTaskName = taskName;this.startTimeNanos = System.nanoTime();}
2. 声明ConfigurableApplicationContext对象
// 声明 ConfigurableApplicationContext 对象ConfigurableApplicationContext context = null;
3. 声明集合容器来存储SpringBootExceptionReporter即启动错误的回调接口
// 声明集合容器用来存储 SpringBootExceptionReporter 启动错误的回调接口Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
4. 设置java.awt.headless的系统属性
// 设置了一个名为java.awt.headless的系统属性
// 其实是想设置该应用程序,即使没有检测到显示器,也允许其启动.
//对于服务器来说,是不需要显示器的,所以要这样设置.
private void configureHeadlessProperty() {System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));}
5. 获取我们之间初始化的监听器(EventPublishingRunListener),并触发starting事件
SpringBoot监听事件相关访问上一篇
6. 创建ApplicationArguments这是一个应用程序的参数持有类
解析启动的时候传递的–开头的参数
// 构造一个应用程序的参数持有类ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
DefaultApplicationArguments构造方法如下:
public DefaultApplicationArguments(String... args) {Assert.notNull(args, "Args must not be null");this.source = new Source(args);this.args = args;}
Source(String[] args) {super(args);}
点击super
public SimpleCommandLinePropertySource(String... args) {super(new SimpleCommandLineArgsParser().parse(args));}
点击parse:在命令行中启动Spring Boot应用时,可以使用–后跟系统属性来传递参数。例如:
java -jar yourapp.jar --server.port=8081
根据上述规则解析给定的String数组,返回一个完全填充的CommandLineArgs对象。Params: args -命令行参数,通常来自main()方法
public CommandLineArgs parse(String... args) {CommandLineArgs commandLineArgs = new CommandLineArgs();for (String arg : args) {if (arg.startsWith("--")) {String optionText = arg.substring(2);String optionName;String optionValue = null;int indexOfEqualsSign = optionText.indexOf('=');if (indexOfEqualsSign > -1) {optionName = optionText.substring(0, indexOfEqualsSign);optionValue = optionText.substring(indexOfEqualsSign + 1);}else {optionName = optionText;}if (optionName.isEmpty()) {throw new IllegalArgumentException("Invalid argument syntax: " + arg);}commandLineArgs.addOptionArg(optionName, optionValue);}else {commandLineArgs.addNonOptionArg(arg);}}return commandLineArgs;}
7. 创建ConfigurableEnvironment这时一个配置环境的对象
// 创建并配置环境ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
spring boot启动时配置properties和yml文件解析过程以及spring.profiles.active多环境下配置文件的解析
这个方法的细节看之前发的这个贴子,里面讲解了Spring Boot在启动过程中加载properties和yaml文件的细节处理过程。
8.配置需要忽略的BeanInfo信息
// 配置需要忽略的BeanInfo信息configureIgnoreBeanInfo(environment);
private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString());}}
9.配置Banner信息对象
// 输出的Banner信息Banner printedBanner = printBanner(environment);
printBanner方法主要实现的就是将自己配置的banner.txt或者图片打印输出到控制台或者日志文件中,如果用户没有自定义输出,就采用默认的输出一个springboot的logo标志,下面的连接详细讲解了这部分的内容。
Spring Boot启动时控制台为何会打印logo以及自定义banner.txt文件控制台打印
10.创建对象的上下文对象
// 创建应用上下文对象context = createApplicationContext();
createApplicationContext的源码为:
SpringApplication的构造方法中有得到webApplicationType的类型是SERVLET
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);}
所以contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = “org.springframework.boot.”
+ “web.servlet.context.AnnotationConfigServletWebServerApplicationContext”;
(ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);是对AnnotationConfigServletWebServerApplicationContext进行实例化,所以看下这个方法的构造方法。
public AnnotationConfigServletWebServerApplicationContext() {this.reader = new AnnotatedBeanDefinitionReader(this);this.scanner = new ClassPathBeanDefinitionScanner(this);}
继续AnnotatedBeanDefinitionReader构造
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {this(registry, getOrCreateEnvironment(registry));}
继续this(registry, getOrCreateEnvironment(registry));
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {Assert.notNull(registry, "BeanDefinitionRegistry must not be null");Assert.notNull(environment, "Environment must not be null");this.registry = registry;this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);}
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);细节如下:
/*** Register all relevant annotation post processors in the given registry.* @param registry the registry to operate on*/public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {registerAnnotationConfigProcessors(registry, null);}
registerAnnotationConfigProcessors会把一些internal开头的路径名对应的类名注册到befactory的beanDefinitionMap和beanDefinitionNames中
org.springframework.context.annotation.internalConfigurationAnnotationProcessor---------> ConfigurationClassPostProcessororg.springframework.context.annotation.internalAutowiredAnnotationProcessor---------> AutowiredAnnotationBeanPostProcessororg.springframework.context.annotation.internalCommonAnnotationProcessor --------->
CommonAnnotationBeanPostProcessororg.springframework.context.event.internalEventListenerProcessor--------->
EventListenerMethodProcessororg.springframework.context.event.internalEventListenerFactory--------->
DefaultEventListenerFactory
registerAnnotationConfigProcessors执行结束之后,
ConfigurationClassPostProcessor的类图如下:
之前在Spring源码的refresh方法中有讲过这个类,主要完成自动装配注解的扫面以及其他相关注解修饰的类生成bean定义信息放到befactory中,交给spring管理其生命周期。
11.加载配置的启动异常的回调异常处理器
// 加载配置的启动异常处理器exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);
spring.factories文件SpringBootExceptionReporter有一个:
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers
getSpringFactoriesInstances会把这个类进行实例化返回给exceptionReporters。