• 面试官:我们简单聊一下SpringBoot的启动流程吧。


    SpringBoot启动原理

    每次看到这个问题总是不能理出一条线来回答流畅,这次理出一条八股文标准答案出来。复习的时候拿出来过一过思路。如果有不合适的地方希望各位大佬指教~

    [源码基于springboot2.4.3]

    框架启动类

    每个SpringBoot项目都有一个标注着@SpringBootApplication注解的main启动类,

    1. @SpringBootApplication
    2. public class Application {
    3. public static void main(String[] args) throws Exception {
    4. SpringApplication.run(Application.class, args);
    5. }
    6. }
    7. 复制代码

    直接看SpringApplication.run方法,往下跟两下发现整个启动流程分为两部分,一个SpringBootApplication构造方法和运行run方法

    1. public static ConfigurableApplicationContext run(Class[] primarySources, String[] args) {
    2. return new SpringApplication(primarySources).run(args);
    3. }
    4. 复制代码

    核心注解

    @SpringBootApplication注解点开之后一堆注解,只需要关注三个核心注解:

    @SpringBootConfiguration

    1. 本质上是包装了一层@Configuration注解,通过JavaConfig方式对bean进行配置,标识当前类为配置类。

    @EnableAutoConfiguration

    1. 点开@EnableAutoConfiguration注解,只看两个核心注解AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)

    2. @AutoConfigurationPackage,这个注解跟@Component注解含义类似,一个用来手动添加注解加载自动配置类一个用来扫描指定路径注入bean。@AutoConfigurationPackage 是为了在一个路径下面有多个自动配置的类要加载,这个注解就不用对每个类单独添加 @Import了,直接引入包路径更方便。@ComponentScan 方便对包路径进行自定义,比如一些 Bean 跟 SpringBootApplication 不在一个路径下面。

    3. @Import(AutoConfigurationImportSelector.class),AutoConfigurationImportSelector类中getCandidateConfigurations()方法,这个方法通过SpringFactoriesLoader.loadFactoryNames()查找位于META-INF/spring.factories文件中的所有自动配置类并加载这些类

    @ComponentScan

    1. 默认扫描当前包以及子包,将有@Component@Controller@Service@Repository等注解的类注册到容器中,以便调用。

    以上的注解只是将需要加载类加载到classpath中,真正去获取类转换成bean的过程还是在run方法中,所以建议回答问题的时候可以先提一下核心注解,启动流程主要还是SpringBootApplcation类的构造方法和run方法。

    构造方法

    1. public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    2. this.resourceLoader = resourceLoader;
    3. Assert.notNull(primarySources, "PrimarySources must not be null");
    4. this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    5. // 获取应用类型,根据是否加载Servlet类判断是否是web环境
    6. this.webApplicationType = WebApplicationType.deduceFromClasspath();
    7. this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
    8. // 读取META-INFO/spring.factories文件,获取对应的ApplicationContextInitializer装配到集合
    9. setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    10. // 设置所有监听器
    11. setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    12. // 推断main函数
    13. this.mainApplicationClass = deduceMainApplicationClass();
    14. }
    15. 复制代码

    可以看到构造方法里主要做了这么几件事:

    1. 根据是否加载servlet类判断是否是web环境

    2. 获取所有初始化器,扫描所有META-INF/spring.factories下的ApplicationContextInitializer子类通过反射拿到实例,在spring实例启动前后做一些回调工作。

    3. 获取所有监听器,同2,也是扫描配置加载对应的类实例。

    4. 定位main方法

    run方法

    1. /**
    2. * Run the Spring application, creating and refreshing a new
    3. * {@link ApplicationContext}.
    4. *
    5. * @param args the application arguments (usually passed from a Java main method)
    6. * @return a running {@link ApplicationContext}
    7. */
    8. public ConfigurableApplicationContext run(String... args) {
    9. // 启动一个秒表计时器,用于统计项目启动时间
    10. StopWatch stopWatch = new StopWatch();
    11. stopWatch.start();
    12. // 创建启动上下文对象即spring根容器
    13. DefaultBootstrapContext bootstrapContext = createBootstrapContext();
    14. // 定义可配置的应用程序上下文变量
    15. ConfigurableApplicationContext context = null;
    16. /**
    17. * 设置jdk系统属性
    18. * headless直译就是无头模式,
    19. * headless模式的意思就是明确Springboot要在无鼠键支持的环境中运行,一般程序也都跑在Linux之类的服务器上,无鼠键支持,这里默认值是true
    20. */
    21. configureHeadlessProperty();
    22. /**
    23. * 获取运行监听器 getRunListeners, 其中也是调用了上面说到的getSpringFactoriesInstances 方法
    24. * 从spring.factories中获取配置
    25. */
    26. SpringApplicationRunListeners listeners = getRunListeners(args);
    27. // 启动监听器
    28. listeners.starting(bootstrapContext, this.mainApplicationClass);
    29. try {
    30. // 包装默认应用程序参数,也就是在命令行下启动应用带的参数,如--server.port=9000
    31. ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
    32. //
    33. /**
    34. * 准备环境 prepareEnvironment 是个硬茬,里面主要涉及到
    35. * getOrCreateEnvironment、configureEnvironment、configurePropertySources、configureProfiles
    36. * environmentPrepared、bindToSpringApplication、attach诸多方法可以在下面的例子中查看
    37. */
    38. ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
    39. // 配置忽略的 bean
    40. configureIgnoreBeanInfo(environment);
    41. // 打印 SpringBoot 标志,即启动的时候在控制台的图案logo,可以在src/main/resources下放入名字是banner的自定义文件
    42. Banner printedBanner = printBanner(environment);
    43. // 创建 IOC 容器
    44. context = createApplicationContext();
    45. // 设置一个启动器,设置应用程序启动
    46. context.setApplicationStartup(this.applicationStartup);
    47. // 配置 IOC 容器的基本信息 (spring容器前置处理)
    48. prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
    49. /**
    50. * 刷新IOC容器
    51. * 这里会涉及Spring容器启动、自动装配、创建 WebServer启动Web服务即SpringBoot启动内嵌的 Tomcat
    52. */
    53. refreshContext(context);
    54. /**
    55. * 留给用户自定义容器刷新完成后的处理逻辑
    56. * 刷新容器后的扩展接口(spring容器后置处理)
    57. */
    58. afterRefresh(context, applicationArguments);
    59. // 结束计时器并打印,这就是我们启动后console的显示的时间
    60. stopWatch.stop();
    61. if (this.logStartupInfo) {
    62. // 打印启动完毕的那行日志
    63. new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
    64. }
    65. // 发布监听应用上下文启动完成(发出启动结束事件),所有的运行监听器调用 started() 方法
    66. listeners.started(context);
    67. // 执行runner,遍历所有的 runner,调用 run 方法
    68. callRunners(context, applicationArguments);
    69. } catch (Throwable ex) {
    70. // 异常处理,如果run过程发生异常
    71. handleRunFailure(context, ex, listeners);
    72. throw new IllegalStateException(ex);
    73. }
    74. try {
    75. // 所有的运行监听器调用 running() 方法,监听应用上下文
    76. listeners.running(context);
    77. } catch (Throwable ex) {
    78. // 异常处理
    79. handleRunFailure(context, ex, null);
    80. throw new IllegalStateException(ex);
    81. }
    82. // 返回最终构建的容器对象
    83. return context;
    84. }
    85. 复制代码

    这一套连招下来谁能背得下来。。。八股文讲究的就是通俗好背,你知道自己是背的,面试官也知道你是背的,但这层窗户纸就不能捅破,到时候去面试背到一半卡壳了多尴尬。

    就背最重要的几个点,反正其他不痛不痒的地方背了也会忘掉。要说最重要的地方当属AbstractApplicationConetxt.refresh方法。

    refresh方法

    这里就不在粘代码了,直接总结成几句话吧,代码多了记不住。refresh方法贯穿bean的生命周期

    invokeBeanFactoryPostProcessors

    1. invokeBeanFactoryPostProcessors方法会找出beanFactory中所有的实现了BeanDefinitionRegistryPostProcessor接口和BeanFactoryPostProcessor接口的bean并执行postProcessor中的postProcessBeanDefinitionRegistry()方法和postProcessBeanFactory()方法

    2. 调用doProcessConfigurationClass方法会处理所有SpringBoot注解标注的所有类,如@Import、@Bean等注解。

    3. 调用BeanDefinitionRegistryPostProcessor实现向容器内添加bean的定义, 调用BeanFactoryPostProcessor实现向容器内添加bean的定义添加属性。(SpringBean的生命周期)

    onRefresh

    创建web容器。如果是web环境当中,会构建一个tomcat web容器。

    总结

    写多了也背不过来,总结一下整体的springboot的启动步骤。

    1. 整个spring框架启动分为两部分,构造SpringBootApplication对象和执行run方法

    2. 核心注解@SpringBootConfiguration标识启动类为配置类,@EnableAutoConfiguration通过内部@Import注解AutoConfigurationImportSelector.class实现自动装配,@ComponentScan默认扫描当前目录及子目录下的bean。

    3. SpringBootApplication的构造方法主要做了几件事。

    • 根据是否加载servlet类判断是否是web环境
    • 获取所有初始化器,扫描所有META-INF/spring.factories下的ApplicationContextInitializer子类通过反射拿到实例,在spring实例启动前后做一些回调工作。
    • 获取所有监听器,同2,也是扫描配置加载对应的类实例。
    • 定位main方法
    1. run方法主要创建了配置环境、事件监听、启动应用上下文,其中rfresh方法贯穿springbean的生命周期,执行bean的生命周期的前后置钩子方法,并且处理spring的注解标注的类。在onRefresh中通过Java代码构建出tomcat容器并启动。

    2. 好了,回家等通知吧

  • 相关阅读:
    Linux基础:2:shell外壳+文件权限
    HTML CSS游戏官网网页模板 大学生游戏介绍网站毕业设计 DW游戏主题网页模板下载 游戏娱乐网页成品代码
    好的代码是优质资产、莫让代码成为负债
    Springboot实现RBAC权限校验
    JIT即时编译
    小团队内部资料共享协作:有效实施策略与方法
    Json格式解析
    R语言随机波动模型SV:马尔可夫蒙特卡罗法MCMC、正则化广义矩估计和准最大似然估计上证指数收益时间序列...
    2_ZYBO FPGA 按键控制蜂鸣器 key_beep=>key_led
    C++ 中 cin 和 getline 的使用
  • 原文地址:https://blog.csdn.net/m0_73311735/article/details/126955850