当前位置: 首页 > 图灵资讯 > 技术篇> 面试官:我们简单聊一下SpringBoot的启动流程吧。

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

来源:图灵教育
时间:2023-06-05 09:27:30

SpringBoot启动原理

每次我看到这个问题,我总是无法理清一条线来顺利回答。这一次,我理清了一个八股文本的标准答案。复习的时候拿出来想一想。如果有不合适的地方,我希望你能给我一些建议~

[源代码基于springboot2.4.3]

框架启动类

每个SpringBoot项目都有一个标有@SpringBotaplication注释的main启动类,

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

直接看Springaplication.run方法,向下和向下发现整个启动过程分为两部分,一个Springbootaplication结构方法和操作run方法。

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

核心注解

@点击SpringBotaplication注释后,只需注意三个核心注释:

@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类中,getcandidateconigurations()方法,这种方法通过SpringFactoriesloader.loadFactoryNames()META位于META-INF/spring.factories文件中的所有自动配置类别,并加载这些类别
@ComponentScan
  1. 默认扫描当前的包和子包,会有@Component@Controller@Service@Repository等注解类注册到容器中,以便调用。

以上注释只是将需要加载的类加载到classpath中,真正获取类转换为bean的过程仍在run方法中,因此建议在回答问题时提到核心注释,启动过程主要是springbootaplcation类的结构方法和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环境是否为web   this.webApplicationType = WebApplicationType.deduceFromClasspath();      this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));      // 读META-INFO/spring.factories文件,获取相应的Applicationcontextinitinitializer组装到集合   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));      // 设置所有监听器   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));      // 推断main函数   this.mainApplicationClass = deduceMainApplicationClass();}

我们可以看到,这些事情主要是在结构方法中完成的:

  1. 根据是否加载servlet,判断web环境是否为web环境
  2. 获取所有初始化器,扫描所有初始化器META-INF/spring.factoriesApplicationcontextinitinitializer子类通过反射获得实例,并在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等服务器上,无鼠键支持,这里的默认值是truee;         */        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);            // 执行runnner,遍历所有的 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;    }

谁能背诵这一套?。。八股文讲究的是通俗易背。你知道你是背的,面试官也知道你是背的,但是这层窗纸不能刺穿。去面试背到一半有多尴尬。

背最重要的几点,反正其他不痛不痒的地方背也会忘记。最重要的是Abstractapplicationconconetxtt。.refresh方法。

refresh方法

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

invokeBeanFactoryPostProcessors
  1. invokeBeanfactoryPostprocesors方法将在beanfactory中找到所有实现beandefinitiongistoryPostprocesors接口和beanfactoryPostprocesors接口的bean,并在postprocesor中执行postprocesprocesbeandefinitiongistory()和postprocesbeanfactory()方法
  2. 使用doprocesconfigurationclass方法将处理所有springboot标记的所有类别,如@Import、@Bean等注释。
  3. 调用BeandefinitionregistryProcesor,将bean定义添加到容器中, 调用BeanFactoryPostProcesor,将Bean定义添加到容器中。(SpringBean的生命周期)
onRefresh

创建web容器。如果是web环境,将构建tomcattat。 web容器。

总结

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

  1. 整个spring框架的启动分为两部分,SpringBootaplication对象构建和执行run方法
  2. 核心注释@SpringBonguration标识启动类为配置类,@enableautonguration通过内部@Import注释autongurationgurationselectortion.class实现自动组装,@ComponentScan默认扫描当前目录和子目录下的bean。
  3. SpringBotaplication的构造方法主要做了几件事。
  • 根据是否加载servlet,判断web环境是否为web环境
  • 获取所有初始化器,扫描所有初始化器META-INF/spring.factoriesApplicationcontextinitinitializer子类通过反射获得实例,并在spring实例启动前后进行一些回调。
  • 获取所有监听器,同2,也是扫描配置加载对应的类实例。
  • 定位main方法
  1. rfresh方法主要创建配置环境、事件监控和启动应用的上下文,其中rfresh方法贯穿springbean的生命周期,执行bean生命周期的前后钩法,并处理spring的注释标记类别。tomcat容器是通过Java代码在onrefresh中构建和启动的。
  2. 好了,回家等通知