https://zhuanlan.zhihu.com/p/295451397
当我们运行SpringBootApplication的时候,调用静态方法run,总体步骤大致可以分为如下几步
image.png

把启动类分解一下,实际上就是两部分:

  • @SpringBootApplication注解
  • 一个main()方法,里面调用SpringApplication.run()方法。

一、@SpringBootApplication注解

查看@SpringBootApplication 的时候就回发现它是一系列注解的组合,分别有@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan

1.1@ComponentScan

@ComponentScan:这个注解的作用是告诉Spring扫描哪个包下面类,加载符合条件的组件(比如@Component@Repository等的类)或者bean的定义。

  • 所有它有一个basePackages的属性,如果默认不写,则从声明@ComponentScan所在类的package进行扫描。所以启动类最好定义在Root package下,因为一般我们使用@SpringBootApplication时,都不指定basePackages的。


1.2@EnableAutoConfiguration

@EnableAutoConfiguration,也是一个复杂注解,关键在于@Import,会加载AutoConfigurationImportSelector.class,会触发 selectImports这个方法,根据返回的String数组(配置类Class的名称)加载配置类
通过getAutoConfigurationEntry()方法点进去,找到getCandidateConfigurations()方法,最后发现 SpringFactoriesLoader.loadFactoryNames()方法。就是SpringFactoriesLoader类,通过loadSpringFactories()方法加载META-INF/spring.factories中的配置类。
image.png
这里使用了spring.factories文件的方式加载配置类,提供了很好的扩展性。
所以@EnableAutoConfiguration注解的作用其实就是开启自动配置,自动配置主要则依靠这种加载方式来实现。

1.3 @SpringBootConfiguration

标注当前类是配置类,并且会将当前类内声明的一个或者多个@Bean注解标记的方法的实例纳入到Spring容器中,并且实例名就是方法名。

1.4小结

使用下面的图片把@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan描述一下
image.png

二、SpringApplication类

接下来讲main方法里执行的这句代码,这是SpringApplication类的静态方法run()。

  1. //启动类的main方法
  2. public static void main(String[] args) {
  3. SpringApplication.run(WorkflowManagementServer.class, args);
  4. }
  5. // 启动类调用的run方法
  6. public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
  7. return run(new Class<?>[] { primarySource }, args);
  8. }
  9. // 和上面的方法区别在于第一个参数是一个数组
  10. public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
  11. return new SpringApplication(primarySources).run(args);
  12. }

通过跟着源码进去,发现最后调用并不是一个静态方法,而是一个实例方法。需要new一个SpringApplication实例,这个构造器还带有一个primarySources的参数。所以我们直接定位到构造器。

  1. public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
  2. this.resourceLoader = resourceLoader;
  3. //断言primarySources不能为null,如果为null,抛出异常提示
  4. Assert.notNull(primarySources, "PrimarySources must not be null");
  5. //启动类传入的Class
  6. this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
  7. //判断当前项目类型,有三种:NONE、SERVLET、REACTIVE
  8. this.webApplicationType = WebApplicationType.deduceFromClasspath();
  9. //设置ApplicationContextInitializer
  10. setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
  11. //设置监听器
  12. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
  13. //判断主类,初始化入口类
  14. this.mainApplicationClass = deduceMainApplicationClass();
  15. }
  16. // 判断主类
  17. private Class<?> deduceMainApplicationClass() {
  18. try {
  19. StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
  20. for (StackTraceElement stackTraceElement : stackTrace) {
  21. if ("main".equals(stackTraceElement.getMethodName())) {
  22. return Class.forName(stackTraceElement.getClassName());
  23. }
  24. }
  25. }
  26. catch (ClassNotFoundException ex) {
  27. // Swallow and continue
  28. }
  29. return null;
  30. }

以上就是创建SpringApplication实例做的事情,下面用张图来表示一下。

image.png
创建SpringApplication实例之后,就完成了SpringApplication类的初始化工作。这个实例里面包括监听器
、初始化器、项目应用类型,启动类集合,类加载器。如下图

image.png
得到SpringApplication实例后,接下来就调用实例方法run()。继续看。

  1. public ConfigurableApplicationContext run(String... args) {
  2. //创建计时器
  3. StopWatch stopWatch = new StopWatch();
  4. //开始计时
  5. stopWatch.start();
  6. //定义上下文对象
  7. ConfigurableApplicationContext context = null;
  8. Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  9. //Headless模式设置
  10. configureHeadlessProperty();
  11. //加载SpringApplicationRunListeners监听器
  12. SpringApplicationRunListeners listeners = getRunListeners(args);
  13. //发送ApplicationStartingEvent事件
  14. listeners.starting();
  15. try {
  16. //封装ApplicationArguments对象
  17. ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
  18. //配置环境模块
  19. ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
  20. //根据环境信息配置要忽略的bean信息
  21. configureIgnoreBeanInfo(environment);
  22. //打印Banner标志
  23. Banner printedBanner = printBanner(environment);
  24. //创建ApplicationContext应用上下文
  25. context = createApplicationContext();
  26. //加载SpringBootExceptionReporter
  27. exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
  28. new Class[] { ConfigurableApplicationContext.class }, context);
  29. //ApplicationContext基本属性配置
  30. prepareContext(context, environment, listeners, applicationArguments, printedBanner);
  31. //刷新上下文
  32. refreshContext(context);
  33. //刷新后的操作,由子类去扩展
  34. afterRefresh(context, applicationArguments);
  35. //计时结束
  36. stopWatch.stop();
  37. //打印日志
  38. if (this.logStartupInfo) {
  39. new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
  40. }
  41. //发送ApplicationStartedEvent事件,标志spring容器已经刷新,此时所有的bean实例都已经加载完毕
  42. listeners.started(context);
  43. //查找容器中注册有CommandLineRunner或者ApplicationRunner的bean,遍历并执行run方法
  44. callRunners(context, applicationArguments);
  45. }
  46. catch (Throwable ex) {
  47. //发送ApplicationFailedEvent事件,标志SpringBoot启动失败
  48. handleRunFailure(context, ex, exceptionReporters, listeners);
  49. throw new IllegalStateException(ex);
  50. }
  51. try {
  52. //发送ApplicationReadyEvent事件,标志SpringApplication已经正在运行,即已经成功启动,可以接收服务请求。
  53. listeners.running(context);
  54. }
  55. catch (Throwable ex) {
  56. //报告异常,但是不发送任何事件
  57. handleRunFailure(context, ex, exceptionReporters, null);
  58. throw new IllegalStateException(ex);
  59. }
  60. return context;
  61. }

结合注释和源码,其实很清晰了,为了加深印象,画张图看一下整个流程。

SpringBoot启动流程 - 图6