您当前的位置:首页 > 电脑百科 > 程序开发 > 框架

面试官:知道 springboot 的启动原理吗?

时间:2020-12-14 13:43:07  来源:  作者:

springboot 启动原理

springboot 常见的启动写法如下:

@SpringBootApplication
public class Application {

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

}

然后我们的程序就可以和 main 方法一样,直接启动运行了。

但是这一切是如何实现的呢?

今天我们一起来学习一下 springboot 的启动原理。

SpringApplication.run 方法

main 方法整体看起来看起来平平无奇。

平平无奇

SpringApplication.run() 让我意识到问题并不简单,我们一起看一下 run 里面到底是如何实现的。

public static ConfigurableApplicationContext run(Object source, String... args) {
    return run(new Object[] { source }, args);
}

这里调用了另外一个方法:

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
    return new SpringApplication(sources).run(args);
}

这里实际上是创建了 SpringApplication 对象,并且执行 run 方法。

SpringApplication

我们简单看一下这个对象。

public SpringApplication(Object... sources) {
    initialize(sources);
}

这里主要是针对 spring 的初始化:

private void initialize(Object[] sources) {
    if (sources != null && sources.length > 0) {
        this.sources.addAll(Arrays.asList(sources));
    }
    this.webEnvironment = deduceWebEnvironment();
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

设置了一些初始化实现、监听器等,此处不做详细展开。

run 方法

构建完成之后,需要调用对应的 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();
    ConfigurableApplicationContext context = null;
    FailureAnalyzers analyzers = null;
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        Banner printedBanner = printBanner(environment);
        context = createApplicationContext();
        analyzers = new FailureAnalyzers(context);
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        refreshContext(context);
        afterRefresh(context, applicationArguments);
        listeners.finished(context, null);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        return context;
    }
    catch (Throwable ex) {
        handleRunFailure(context, listeners, analyzers, ex);
        throw new IllegalStateException(ex);
    }
}

我们这里大概梳理一下启动过程的步骤:

1. 初始化监听器,以及添加到SpringApplication的自定义监听器。

2. 发布ApplicationStartedEvent事件,如果想监听ApplicationStartedEvent事件,你可以这样定义:public class ApplicationStartedListener implements ApplicationListener,然后通过SpringApplication.addListener(..)添加进去即可。

3. 装配参数和环境,确定是web环境还是非web环境。

4. 装配完环境后,就触发ApplicationEnvironmentPreparedEvent事件。

5. 如果SpringApplication的showBanner属性被设置为true,则打印启动的Banner。

6. 创建ApplicationContext,会根据是否是web环境,来决定创建什么类型的ApplicationContext。

7. 装配Context的环境变量,注册Initializers、beanNameGenerator等。

8. 发布ApplicationPreparedEvent事件。

9. 注册springApplicationArguments、springBootBanner,加载资源等

10. 遍历调用所有SpringApplicationRunListener的contextLoaded()方法。

11. 调用ApplicationContext的refresh()方法,装配context beanfactory等非常重要的核心组件。

12. 查找当前ApplicationContext中是否注册有CommandLineRunner,如果有,则遍历执行它们。

13. 发布ApplicationReadyEvent事件,启动完毕,表示服务已经可以开始正常提供服务了。通常我们这里会监听这个事件来打印一些监控性质的日志,表示应用正常启动了。
面试官:知道 springboot 的启动原理吗?

 

启动流程

@SpringBootApplication 注解

看完了静态方法,我们来看一下另一个注解 @SpringBootApplication。

注解定义

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    // 省略方法
}

我们省略掉对应的方法属性,发现实际上这个注解是由 3 个注解组合而成:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan

其中 @SpringbootConfiguration 是完全等价于 @Configuration 的,此处应该是为了和 spring 的注解做区分。

所以一开始的实现,等价于:

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application {

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

}

当然了, springboot 的理念就是极简配置,能少写一行代码,就少写一行代码!

@Configuration 注解

这里的 @Configuration 大家应该并不陌生,spring 中可以使用下面的写法,替代 spring xml 的配置写法:

@Configuration
public class MockConfiguration{
    @Bean
    public MockService mockService(){
        return new MockServiceImpl(dependencyService());
    }

    @Bean
    public DependencyService dependencyService(){
        return new DependencyServiceImpl();
    }
}

@ComponentScan 注解

@ComponentScan 的功能其实就是自动扫描并加载符合条件的组件(比如 @Component 和 @Service等)或者bean定义,最终将这些bean定义加载到IoC容器中。

我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认Spring框架实现会从声明 @ComponentScan 所在类的package进行扫描。

ps: 所以我们的 Application 启动类一般是放在根目录,这样连扫描的包也省略掉了。

@EnableAutoConfiguration 注解

这个注解我们放在最后讲解,因为它为 springboot 带来了更多的便利性。

注解定义

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

这个注解实际上是一个组合注解 @AutoConfigurationPackage + @Import

@AutoConfigurationPackage:自动配置包

@Import: 导入自动配置的组件

我们来看一下这 2 个注解:

@AutoConfigurationPackage 注解

这个注解主要是通过 @Import 注解导入了 AutoConfigurationPackages.Registrar.class 类。

实现如下:

/**
 * {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
 * configuration.
 * @author 老马啸西风
 */
@Order(Ordered.HIGHEST_PRECEDENCE)
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata,
            BeanDefinitionRegistry registry) {
        register(registry, new PackageImport(metadata).getPackageName());
    }
    @Override
    public Set<Object> determineImports(AnnotationMetadata metadata) {
        return Collections.<Object>singleton(new PackageImport(metadata));
    }
}

它其实是注册了一个Bean的定义。

new PackageImport(metadata).getPackageName(),它其实返回了当前主程序类的同级以及子级的包组件。

@Import(EnableAutoConfigurationImportSelector.class)

我们来看一下另外一个注解,@Import(EnableAutoConfigurationImportSelector.class)。

EnableAutoConfigurationImportSelector 实现如下:

public class EnableAutoConfigurationImportSelector
        extends AutoConfigurationImportSelector {

    @Override
    protected boolean isEnabled(AnnotationMetadata metadata) {
        if (getClass().equals(EnableAutoConfigurationImportSelector.class)) {
            return getEnvironment().getProperty(
                    EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,
                    true);
        }
        return true;
    }

}

这个方法一眼看上去也是平平无奇,因为核心实现都在父类中。

最核心的方法如下:

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    // 不启用,直接返回无导入
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    try {
        AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
                .loadMetadata(this.beanClassLoader);
        AnnotationAttributes attributes = getAttributes(annotationMetadata);

        // 这一行回去加载 springboot 指定的文件
        List<String> configurations = getCandidateConfigurations(annotationMetadata,
                attributes);
        configurations = removeDuplicates(configurations);
        configurations = sort(configurations, autoConfigurationMetadata);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = filter(configurations, autoConfigurationMetadata);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return configurations.toArray(new String[configurations.size()]);
    }
    catch (IOException ex) {
        throw new IllegalStateException(ex);
    }
}

我们用过的各种 springboot-starter,使用起来引入一个 jar 就可以使用了。

都要归功于下面这个方法:

// 这一行回去加载 springboot 指定的文件
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

这里实际上回去解析一个文件:

Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");

这也就是我们在开发自己的 springboot-starter 时,为什么需要把自己的启动类放在 META-INF/spring.factories 文件中的原因,这样就可以被 springboot 加载,并且生效了。

推荐阅读:

Spring Boot-11-自定义 springboot starter

小结

到这里,springboot 的启动原理就讲解的差不多了。

springboot 和以前的 spring xml 配置相比较,确实简化了太多太多。

让我们可以更加快速,正确的启动一个 java web 程序。

未来的发展历程也必然是这样,谁更加简单便捷,谁能提升效率,就是谁的天下。这就是老马的效率第一定律。

希望本文对你有帮助,如果有其他想法的话,也可以评论区和大家分享哦。

各位极客的点赞收藏转发,是老马持续写作的最大动力!

期待与你的下次重逢。



Tags:springboot   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
我是一名程序员关注我们吧,我们会多多分享技术和资源。进来的朋友,可以多了解下青锋的产品,已开源多个产品的架构版本。Thymeleaf版(开源)1、采用技术: springboot、layui、Thymel...【详细内容】
2021-12-14  Tags: springboot  点击:(20)  评论:(0)  加入收藏
前言项目中的配置文件会有密码的存在,例如数据库的密码、邮箱的密码、FTP的密码等。配置的密码以明文的方式暴露,并不是一种安全的方式,特别是大型项目的生产环境中,因为配置文...【详细内容】
2021-11-17  Tags: springboot  点击:(25)  评论:(0)  加入收藏
SpringBoot开发的物联网通信平台系统项目功能模块 功能 说明 MQTT 1.SSL支持 2.集群化部署时暂不支持retain&will类型消 UDP ...【详细内容】
2021-11-05  Tags: springboot  点击:(55)  评论:(0)  加入收藏
1. 介绍1.1 介绍今天开始我们来学习Java操作MySQL数据库的技巧,Java操作MySQL是借助JdbcTemplate这个对象来实现的。JdbcTemplate是一个多数据库集中解决方案,而我们今天只讲...【详细内容】
2021-11-05  Tags: springboot  点击:(30)  评论:(0)  加入收藏
SpringBoot中的Controller注册本篇将会以Servlet为切入点,通过源码来看web容器中的Controller是如何注册到HandlerMapping中。请求来了之后,web容器是如何根据请求路径找到对...【详细内容】
2021-11-04  Tags: springboot  点击:(52)  评论:(0)  加入收藏
环境:Springboot2.4.11环境配置接下来的演示都是基于如下接口进行。@RestController@RequestMapping("/exceptions")public class ExceptionsController { @GetMapping(...【详细内容】
2021-10-11  Tags: springboot  点击:(41)  评论:(0)  加入收藏
SpringBoot项目默认使用logback, 已经内置了 logback 的相关jar包,会从resource包下查找logback.xml, logback 文件格式范本 可直接复制使用,有控制台 info.log error.log三个...【详细内容】
2021-10-09  Tags: springboot  点击:(50)  评论:(0)  加入收藏
环境:Springboot2.4.10当应用程序启动时,Spring Boot将自动从以下位置查找并加载application.properties和application.yaml文件: 从Classpath类路径classpath的根类路径classp...【详细内容】
2021-09-26  Tags: springboot  点击:(76)  评论:(0)  加入收藏
搭建基础1. Intellij IDEA 2. jdk1.8 3. maven3.6.3搭建方式(1)在线创建项目Spring Boot 官方提供的一种创建方式,在浏览器中访问如下网址: https://start.spring.io/在打开的页...【详细内容】
2021-09-14  Tags: springboot  点击:(78)  评论:(0)  加入收藏
最近开发项目的时候需要用到对象的属性拷贝,以前也有用过一些复制框架,比如spring的 BeanUtils.copyProperties等方式,但总是不尽如人意,最近发现使用orika进行对象拷贝挺好用的...【详细内容】
2021-08-27  Tags: springboot  点击:(231)  评论:(0)  加入收藏
▌简易百科推荐
近日只是为了想尽办法为 Flask 实现 Swagger UI 文档功能,基本上要让 Flask 配合 Flasgger, 所以写了篇 Flask 应用集成 Swagger UI 。然而不断的 Google 过程中偶然间发现了...【详细内容】
2021-12-23  Python阿杰    Tags:FastAPI   点击:(6)  评论:(0)  加入收藏
文章目录1、Quartz1.1 引入依赖<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.2</version></dependency>...【详细内容】
2021-12-22  java老人头    Tags:框架   点击:(11)  评论:(0)  加入收藏
今天来梳理下 Spring 的整体脉络啦,为后面的文章做个铺垫~后面几篇文章应该会讲讲这些内容啦 Spring AOP 插件 (了好久都忘了 ) 分享下 4ye 在项目中利用 AOP + MybatisPlus 对...【详细内容】
2021-12-07  Java4ye    Tags:Spring   点击:(14)  评论:(0)  加入收藏
&emsp;前面通过入门案例介绍,我们发现在SpringSecurity中如果我们没有使用自定义的登录界面,那么SpringSecurity会给我们提供一个系统登录界面。但真实项目中我们一般都会使用...【详细内容】
2021-12-06  波哥带你学Java    Tags:SpringSecurity   点击:(18)  评论:(0)  加入收藏
React 简介 React 基本使用<div id="test"></div><script type="text/javascript" src="../js/react.development.js"></script><script type="text/javascript" src="../js...【详细内容】
2021-11-30  清闲的帆船先生    Tags:框架   点击:(19)  评论:(0)  加入收藏
流水线(Pipeline)是把一个重复的过程分解为若干个子过程,使每个子过程与其他子过程并行进行的技术。本文主要介绍了诞生于云原生时代的流水线框架 Argo。 什么是流水线?在计算机...【详细内容】
2021-11-30  叼着猫的鱼    Tags:框架   点击:(21)  评论:(0)  加入收藏
TKinterThinter 是标准的python包,你可以在linx,macos,windows上使用它,你不需要安装它,因为它是python自带的扩展包。 它采用TCL的控制接口,你可以非常方便地写出图形界面,如...【详细内容】
2021-11-30    梦回故里归来  Tags:框架   点击:(26)  评论:(0)  加入收藏
前言项目中的配置文件会有密码的存在,例如数据库的密码、邮箱的密码、FTP的密码等。配置的密码以明文的方式暴露,并不是一种安全的方式,特别是大型项目的生产环境中,因为配置文...【详细内容】
2021-11-17  充满元气的java爱好者  博客园  Tags:SpringBoot   点击:(25)  评论:(0)  加入收藏
一、搭建环境1、创建数据库表和表结构create table account(id INT identity(1,1) primary key,name varchar(20),[money] DECIMAL2、创建maven的工程SSM,在pom.xml文件引入...【详细内容】
2021-11-11  AT小白在线中  搜狐号  Tags:开发框架   点击:(29)  评论:(0)  加入收藏
SpringBoot开发的物联网通信平台系统项目功能模块 功能 说明 MQTT 1.SSL支持 2.集群化部署时暂不支持retain&will类型消 UDP ...【详细内容】
2021-11-05  小程序建站    Tags:SpringBoot   点击:(55)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条