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

34次阅读

共计 6636 个字符,预计需要花费 17 分钟才能阅读完成。

思维导图

前言

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 的启动类剖析就讲到这里了,感激大家的浏览。

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

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

正文完
 0