首页 > 其他分享 >面试官:我们简单聊一下SpringBoot的启动流程吧。

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

时间:2023-06-04 18:33:36浏览次数:38  
标签:容器 面试官 run SpringBoot 启动 spring 流程 context 注解


SpringBoot启动原理

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

[源码基于springboot2.4.3]

框架启动类

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

@SpringBootApplication
public class Application {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(Application.class, args);
    }
}

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

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
   return new SpringApplication(primarySources).run(args);
}

核心注解

@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方法。

构造方法

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

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

  1. 根据是否加载servlet类判断是否是web环境
  2. 获取所有初始化器,扫描所有META-INF/spring.factories下的ApplicationContextInitializer子类通过反射拿到实例,在spring实例启动前后做一些回调工作。
  3. 获取所有监听器,同2,也是扫描配置加载对应的类实例。
  4. 定位main方法

run方法

/**
     * 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}
     */
    public ConfigurableApplicationContext run(String... args) {
        // 启动一个秒表计时器,用于统计项目启动时间
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        // 创建启动上下文对象即spring根容器
        DefaultBootstrapContext bootstrapContext = createBootstrapContext();
        // 定义可配置的应用程序上下文变量
        ConfigurableApplicationContext context = null;
        /**
         * 设置jdk系统属性
         * headless直译就是无头模式,
         * headless模式的意思就是明确Springboot要在无鼠键支持的环境中运行,一般程序也都跑在Linux之类的服务器上,无鼠键支持,这里默认值是true;
         */
        configureHeadlessProperty();
        /**
         * 获取运行监听器 getRunListeners, 其中也是调用了上面说到的getSpringFactoriesInstances 方法
         * 从spring.factories中获取配置
         */
        SpringApplicationRunListeners listeners = getRunListeners(args);
        // 启动监听器
        listeners.starting(bootstrapContext, this.mainApplicationClass);
        try {
            // 包装默认应用程序参数,也就是在命令行下启动应用带的参数,如--server.port=9000
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            //
            /**
             * 准备环境 prepareEnvironment 是个硬茬,里面主要涉及到
             * getOrCreateEnvironment、configureEnvironment、configurePropertySources、configureProfiles
             * environmentPrepared、bindToSpringApplication、attach诸多方法可以在下面的例子中查看
             */
            ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            // 配置忽略的 bean
            configureIgnoreBeanInfo(environment);
            // 打印 SpringBoot 标志,即启动的时候在控制台的图案logo,可以在src/main/resources下放入名字是banner的自定义文件
            Banner printedBanner = printBanner(environment);
            // 创建 IOC 容器
            context = createApplicationContext();
            // 设置一个启动器,设置应用程序启动
            context.setApplicationStartup(this.applicationStartup);
            // 配置 IOC 容器的基本信息 (spring容器前置处理)
            prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            /**
             * 刷新IOC容器
             * 这里会涉及Spring容器启动、自动装配、创建 WebServer启动Web服务即SpringBoot启动内嵌的 Tomcat
             */
            refreshContext(context);
            /**
             * 留给用户自定义容器刷新完成后的处理逻辑
             * 刷新容器后的扩展接口(spring容器后置处理)
             */
            afterRefresh(context, applicationArguments);
            // 结束计时器并打印,这就是我们启动后console的显示的时间
            stopWatch.stop();
            if (this.logStartupInfo) {
                // 打印启动完毕的那行日志
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            // 发布监听应用上下文启动完成(发出启动结束事件),所有的运行监听器调用 started() 方法
            listeners.started(context);
            // 执行runner,遍历所有的 runner,调用 run 方法
            callRunners(context, applicationArguments);
        } catch (Throwable ex) {
            // 异常处理,如果run过程发生异常
            handleRunFailure(context, ex, listeners);
            throw new IllegalStateException(ex);
        }

        try {
            // 所有的运行监听器调用 running() 方法,监听应用上下文
            listeners.running(context);
        } catch (Throwable ex) {
            // 异常处理
            handleRunFailure(context, ex, null);
            throw new IllegalStateException(ex);
        }
        // 返回最终构建的容器对象
        return context;
    }

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

就背最重要的几个点,反正其他不痛不痒的地方背了也会忘掉。要说最重要的地方当属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. 好了,回家等通知吧

标签:容器,面试官,run,SpringBoot,启动,spring,流程,context,注解
From: https://blog.51cto.com/u_15966050/6411643

相关文章

  • springboot整合mybatis
    整合mybatis基础配置启动类添加@MapperScan配置文件#=================================数据库相关配置====================================spring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.datasource.url=jdbc:mysql://10.1.1.1:3306/db?useUnico......
  • 从日志记一次Spring事务完整流程
    spring事务一次完整流程,创建》确认获取连接》完成》提交》释放链接DataSourceTransactionManager//Step1.进入业务方法前,依据事物切面创建事务对象2019-07-0622:34:24,819[main]DEBUGo.s.j.d.DataSourceTransactionManager-Creatingnewtransactionwithname[com......
  • 【视频】SpringBoot为啥到了不学不行的阶段
    Spring常用注解redis视频集合,看完这些别说不会redis01-SpringBoot视频教程_SpringBoot简介.rar02-SpringBoot视频教程_SpringBoot主要特性.rar03-SpringBoot视频教程_SpringBoot四大核心功能.rar04-SpringBoot视频教程_SpringBoot基础开发环境.rar05-SpringBoot视频教程_SpringBoot......
  • 前置知识:方法递归的算法、执行流程详解
       ......
  • Java基础知识:面试官必问的问题
    数据类型基本类型byte/8char/16short/16int/32float/32long/64double/64boolean/~boolean只有两个值:true、false,可以使用1bit来存储,但是具体大小没有明确规定。JVM会在编译时期将boolean类型的数据转换为int,使用1来表示true,0表示false。JVM支持boolean......
  • Java基础知识:面试官必问的问题
    数据类型基本类型byte/8char/16short/16int/32float/32long/64double/64boolean/~boolean只有两个值:true、false,可以使用1bit来存储,但是具体大小没有明确规定。JVM会在编译时期将boolean类型的数据转换为int,使用1来表示true,0表示false。JVM支持boolean......
  • 【了不起的芯片 - 读书笔记】CPU 的制作流程 ( 晶圆制作 | 光刻机光刻流程 | 蚀刻过程
    文章目录一、晶圆制作二、光刻机光刻流程三、蚀刻过程四、涂层过程五、重复上述步骤若干次六、芯片封装一、晶圆制作晶圆制作是半导体芯片制造的关键过程,它涉及将硅晶片(或其他半导体材料)转化为可以用于集成电路制造的基础材料。下面是晶圆制作的主要步骤:单晶生长:通过化学气相沉......
  • 搭建一个属于自己的springboot项目
    一、确定环境最近公司要上个新系统,指定由我来带两个人进行开发,既然是新项目,那么项目搭建的事就落到我的头上了。现在都是使用springboot进行开发,为此我搭环境使用的是springboot,具体java环境如下,使用springboot的版本是2.3.3.RELEASE。使用maven进行项目管理,总结下,我使用到的......
  • JAVA的springboot+vue医疗预约服务管理信息系统,医院预约管理系统,附源码+数据库+论文+P
    1、项目介绍会员制医疗预约服务管理信息系统是针对会员制医疗预约服务管理方面必不可少的一个部分。在会员制医疗预约服务管理的整个过程中,会员制医疗预约服务管理系统担负着最重要的角色。为满足如今日益复杂的管理需求,各类的管理系统也在不断改进。本课题所设计的是会员制医疗......
  • SpringBoot 使用事务报错:No transaction aspect-managed TransactionStatus in scope
    当使用SpringBoot进行开发时,你可能会遇到以下错误之一:“Notransactionaspect-managedTransactionStatusinscope”。这个错误通常发生在方法中手动回滚事务的情况下,但方法本身没有被@Transactional注解修饰。在本文中,我们将深入探讨这个错误的原因以及如何解决它。我们将提供......