乐趣区

关于后端:SpringBoot启动流程是怎样的

思维导图

前言

SpringBoot 一开始最让我印象粗浅的就是通过一个启动类就能启动利用。在 SpringBoot 以前,启动利用尽管也不麻烦,然而还是有点繁琐,要打包成 war 包,又要配置 tomcat,tomcat 又有一个 server.xml 文件去配置。

然而 SpringBoot 则内置了 tomcat,通过启动类启动,配置也集中在一个 application.yml 中,几乎不要太难受。好奇心驱动,于是我很想搞清楚启动类的启动过程,那么开始吧。

《2020 最新 Java 根底精讲视频教程和学习路线!》

一、启动类

首先咱们看最常见的启动类写法。

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

把启动类合成一下,实际上就是两局部:

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

二、@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 {} 

很显著,@SpringBootApplication 注解由三个注解组合而成,别离是:

  • @ComponentScan
  • @EnableAutoConfiguration
  • @SpringBootConfiguration

2.1 @ComponentScan

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {} 

这个注解的作用是 通知 Spring 扫描哪个包上面类,加载符合条件的组件 (比方贴有 @Component 和 @Repository 等的类) 或者 bean 的定义。

所以有一个 basePackages 的属性,如果默认不写,则从申明 @ComponentScan 所在类的 package 进行扫描。

所以 启动类最好定义在 Root package 下,因为个别咱们在应用 @SpringBootApplication 时,都不指定 basePackages 的。

2.2 @EnableAutoConfiguration

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

这是一个复合注解,看起来很多注解,实际上要害在 @Import 注解,它会加载 AutoConfigurationImportSelector 类,而后就会触发这个类的 selectImports()办法。依据返回的 String 数组 (配置类的 Class 的名称) 加载配置类。

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {// 返回的 String[]数组,是配置类 Class 的类名
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}
        AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
        // 返回配置类的类名
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
} 

咱们始终点上来,就能够找到最初的幕后英雄,就是 SpringFactoriesLoader 类,通过 loadSpringFactories()办法加载 META-INF/spring.factories 中的配置类。

这里应用了 spring.factories 文件的形式加载配置类,提供了很好的扩展性。

所以 @EnableAutoConfiguration 注解的作用其实就是开启主动配置,主动配置次要则依附这种加载形式来实现。

2.3 @SpringBootConfiguration

@SpringBootConfiguration 继承自 @Configuration,二者性能也统一,标注以后类是配置类,并会将以后类内申明的一个或多个以 @Bean 注解标记的办法的实例纳入到 spring 容器中,并且实例名就是办法名。

2.4 小结

咱们在这里画张图把 @SpringBootApplication 注解蕴含的三个注解别离解释一下。

三、SpringApplication 类

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

// 启动类的 main 办法
public static void main(String[] args) {SpringApplication.run(SpringmvcApplication.class, args);
}

// 启动类调的 run 办法
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    // 调的是上面的,参数是数组的 run 办法
    return run(new Class<?>[] {primarySource}, args);
}

// 和下面的办法区别在于第一个参数是一个数组
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {// 实际上 new 一个 SpringApplication 实例,调的是一个实例办法 run()
    return new SpringApplication(primarySources).run(args);
} 

通过下面的源码,发现实际上最初调的并不是静态方法,而是实例办法,须要 new 一个 SpringApplication 实例,这个结构器还带有一个 primarySources 的参数。所以咱们间接定位到结构器。

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    // 断言 primarySources 不能为 null,如果为 null,抛出异样提醒     Assert.notNull(primarySources, "PrimarySources must not be null");
    // 启动类传入的 Class     this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 判断以后我的项目类型,有三种:NONE、SERVLET、REACTIVE     this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 设置 ApplicationContextInitializer     setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 设置监听器     setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 判断主类,初始化入口类     this.mainApplicationClass = deduceMainApplicationClass();}

// 判断主类 private Class<?> deduceMainApplicationClass() {
    try {StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
        for (StackTraceElement stackTraceElement : stackTrace) {if ("main".equals(stackTraceElement.getMethodName())) {return Class.forName(stackTraceElement.getClassName());
            }
        }
    }
    catch (ClassNotFoundException ex) {// Swallow and continue}
    return null;
} 

以上就是创立 SpringApplication 实例做的事件,上面用张图来示意一下。

创立了 SpringApplication 实例之后,就实现了 SpringApplication 类的初始化工作,这个实例里包含监听器、初始化器,我的项目利用类型,启动类汇合,类加载器。如图所示。

失去 SpringApplication 实例后,接下来就调用实例办法 run()。持续看。

public ConfigurableApplicationContext run(String... args) {
    // 创立计时器
    StopWatch stopWatch = new StopWatch();
    // 开始计时
    stopWatch.start();
    // 定义上下文对象
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    //Headless 模式设置
    configureHeadlessProperty();
    // 加载 SpringApplicationRunListeners 监听器
    SpringApplicationRunListeners listeners = getRunListeners(args);
    // 发送 ApplicationStartingEvent 事件
    listeners.starting();
    try {
        // 封装 ApplicationArguments 对象
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
        // 配置环境模块
        ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
        // 依据环境信息配置要疏忽的 bean 信息
        configureIgnoreBeanInfo(environment);
        // 打印 Banner 标记
        Banner printedBanner = printBanner(environment);
        // 创立 ApplicationContext 利用上下文
        context = createApplicationContext();
        // 加载 SpringBootExceptionReporter
        exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                                                         new Class[] { ConfigurableApplicationContext.class}, context);
        //ApplicationContext 根本属性配置
        prepareContext(context, environment, listeners, applicationArguments, printedBanner);
        // 刷新上下文
        refreshContext(context);
        // 刷新后的操作,由子类去扩大
        afterRefresh(context, applicationArguments);
        // 计时完结
        stopWatch.stop();
        // 打印日志
        if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
        }
        // 发送 ApplicationStartedEvent 事件,标记 spring 容器曾经刷新,此时所有的 bean 实例都曾经加载结束
        listeners.started(context);
        // 查找容器中注册有 CommandLineRunner 或者 ApplicationRunner 的 bean,遍历并执行 run 办法
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        // 发送 ApplicationFailedEvent 事件,标记 SpringBoot 启动失败
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }

    try {
        // 发送 ApplicationReadyEvent 事件,标记 SpringApplication 曾经正在运行,即曾经胜利启动,能够接管服务申请。listeners.running(context);
    }
    catch (Throwable ex) {
        // 报告异样,然而不发送任何事件
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    return context;
} 

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

总结

外表启动类看起来就一个 @SpringBootApplication 注解,一个 run()办法。其实是通过高度封装后的后果。咱们能够从这个剖析中学到很多货色。比方应用了 spring.factories 文件来实现主动配置,进步了扩展性。在启动时应用观察者模式,以事件公布的模式告诉,升高耦合,易于扩大等等。

那么 SpringBoot 的启动类剖析就讲到这里了,感激大家的浏览。

感觉有用就点个赞吧,你的点赞是我创作的最大能源~

我是一个致力让大家记住的程序员。咱们下期再见!!!

退出移动版