源码解读
掘金网址

springBoot的run()分析

一、重要注解

1、主配置类

  1. @SpringBootApplication
  2. public class SpringDemoApplication {
  3. public static void main(String[] args) {
  4. SpringApplication.run(SpringDemoApplication.class, args);
  5. }
  6. }

@SpringBootApplication中的三个重要注解
image.png

2、@SpringBootConfiguration

打开@springbootConfiguration注解可以看到就是一个@Configuration注解和一些其他元注解的组合,可以看作是springboot将spring的Configuration注解进行一个包装,这就说明SpringBoot的启动类会加入Spring容器。
image.png

@Configuration
在Spring4以后,官方推荐使用 Java Config 来代替 Application.xml 声明将Bean交给容器管理。在Spring Boot 中,Java Config 使用完全代替了application.xml 实现了xml的零配置, 开下面这个例子

  • 创建一个bean类

    1. public class SomeBean {
    2. public void doWork() {
    3. System.out.println("do work...");
    4. }
    5. }
  • 其中,dowork是逻辑方法 再创建一个Config类

    1. @Configuration
    2. public class Config {
    3. @Bean
    4. public SomeBean someBean() {
    5. return new SomeBean();
    6. }
    7. }

    在这里,在Config类上添加了一个@configuration注解,可以理解为Spring中的配置类,其返回值为someBean,someBean方法上也添加了一个@bean注解,其返回对象也将会交由Spring容器进行管理。

  • 测试

    1. public class Test {
    2. public static void main(String[] args) {
    3. ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
    4. SomeBean sb = context.getBean(SomeBean.class);
    5. sb.doWork();
    6. }
    7. }

    这里,创建了一个AnnotationConfigApplicationContext对象,传入了Config.class 后,得到了someBean对象。

3、@EnableAutoConfiguration

  • @AutoConfigurationPackage注解就是扫描跟主配置类(就是有main方法的那个类)同级目录以及子目录下的包,这也是什么我上一节说的springboot其他的包必须在主配置类同级或者子目录以下的原因,放在其他地方,扫描不到嘛!
  • @Import(AutoConfigurationImportSelector.class)注解,借助AutoConfigurationImportSelector,将所有符合条件的@Configuration 配置加载到IOC容器中。

image.png
而最主要的还需要借助于 Spring 框架的一个工具类,SpringFactoriestLoader 将META-INF/spring.factories加载配置,spring.factories文件是一个典型的properties配置文件,配置格式为key=value形式,不过key和value都是完整的类名。
image.png
(遍历所有jar包下)”META-INF/spring.factories”这个路径下放了一些什么组件
image.png
打开spring.factories,可以看到很多的东西,我们找到这一个地方

image.png
上图里面这么多的xxxAutoConfiguration就是我们的这么久得出的结果,最终就是加载这么多的类的全路径,然后springboot内部就是实例化这些类并加载到容器里面,完成springboot应用启动时的自动配置
例如 :我们使用Aop也好,事务也好,缓存也好,零配置MVC也好,mybatis配置也好,都要手动开启功能,才能用。而在SpringBoot项目中,无需手动配置

4、@ComponentScan

@ComponentScan注解,用于类或接口上主要指定的扫描路径,Spring会把指定路径下带有指定注解的类自动装配到bean容器里,会被自动装配的注解包括@Controller,@Service,@Component,@Repository等。其作用相当于

  1. <context:component-scan base-package=”com.maple.learn />

5、运行过程

1、springboot应用启动———>
2、@SpringBootApplication起作用————->
3、@EnableAutoConfiguration————->
4、【@AutoConfigurationPackage:扫描主配置类同级目录以及子包】【@Import({EnableAutoConfigurationImportSelector.class}):导入一个自动配置组件】—————>
5、EnableAutoConfigurationImportSelector extends AutoConfigurationImportSelector ——————>
6、public String[] selectImports() {List configurations = this.getCandidateConfigurations(annotationMetadata, attributes);}—————>
7、List configurations = SpringFactoriesLoader.loadFactoryNames()—————>
8、classLoader.getResources(“META-INF/spring.factories”)———————->
9、spring-boot-autoconfigure.5.9.RELEASEspring-boot-autoconfigure-1.5.9.RELEASE.jar!META-INFspring.factories————->
10、factories里面的各种xxxxAutoConfiguration的全类名

二、SpringBoot的run()过程

下面我们查看run()方法内部的源码,核心代码具体如下:
image.png

从上述源码可以看出,SpringApplication.run()方法内部执行了两个操作,
分别是SpringApplication实例的初始化创建和调用run()启动项目,这两个阶段的实现具体说明如下:

1、SpringApplication实例的初始化创建

查看SpringApplication实例对象初始化创建的源码信息,核心代码具体如下 :
image.png

image.png
从上述源码可以看出,SpringApplication的初始化过程主要包括4部分,具体说明如下。

  • this.webApplicationType=WebApplicationType.deduceFromClasspath()用于判断当前webApplicationType应用的类型deduceFromClasspath()方法用于查看Classpath类路径下是否存在某个特征类,从而判断当前webApplicationType类型是SERVLET应用(Spring 5之前的传统MVC应用)还是REACTIVE应用(Spring 5开始出现的WebFlux交互式应用)

  • this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class))用于SpringApplication应用的初始化器设置。在初始化器设置过程中,会使用Spring类加载器SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下的spring.factores文件中获取所有可用的应用初始化器ApplicationContextInitializer。

  • this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class))用于SpringApplication应用的监听器设置。监听器设置的过程与上一步初始化器设置的过程基本一样,也是使用SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下的spring.factores文件中获取所有可用的监听器类ApplicationListener。

  • this.mainApplicationClass = this.deduceMainApplicationClass()
    用于推断并设置项目main()方法启动的主程序启动类

2、项目的初始化启动

分析完(new SpringApplication(primarySources)).run(args)源码前一部分SpringApplication实例对象的初始化创建后,查看run(args)方法执行的项目初始化启动过程,核心代码具体如下:
image.png
image.png

从上述源码可以看出,项目初始化启动过程大致包括以下部分:

第一步:获取并启动监听器。this.getRunListeners(args)和listeners.starting()方法主要用于获取SpringApplication实 例初始化过程中初始化的SpringApplicationRunListener监听器并运行。

第二步:根据SpringApplicationRunListeners以及参数来准备环境this.prepareEnvironment(listeners, applicationArguments)方法主要用于对项目运行环境进 行预设置,同时通过this.configureIgnoreBeanInfo(environment)方法排除一些不需要的运行环境。

第三步:创建Spring容器。根据webApplicationType进行判断, 确定容器类型,如果该类型为SERVLET类型,会通过反射装载 对应的字节码,也就是AnnotationConfigServletWebServerApplicationContext,接着使用之前 初始化设置的context(应用上下文环境)、environment(项目运行环境)、listeners(运行监听 器)、applicationArguments(项目参数)和printedBanner(项目图标信息)进行应用上下文的组 装配置,并刷新配置。

第四步:Spring容器前置处理。这一步主要是在容器刷新之前的准备动作。设置容器环境,包括各种变量等等,其中包含一个非常关键的操 作:将启动类注入容器,为后续开启自动化配置奠定基础。

第五步:刷新容器。开启刷新spring容器,通过refresh方法对整个IOC容器的初始化(包括bean资源的定位,解析,注册等 等),同时向JVM运行时注册一个关机钩子,在JVM关机时会关闭这个上下文,除非当时它已经关闭。

第六步:Spring容器后置处理。扩展接口,设计模式中的模板方法,默认为空实现。如果有自定义需求,可以重写该方法。比如打印一些启 动结束log,或者一些其它后置处理。

第七步:发出结束执行的事件。获取EventPublishingRunListener监听器,并执行其started方法,并且将创建的Spring容器传进去 了,创建一个ApplicationStartedEvent事件,并执行ConfigurableApplicationContext 的publishEvent方法,也就是说这里是在Spring容器中发布事件,并不是在SpringApplication中发布 事件,和前面的starting是不同的,前面的starting是直接向SpringApplication中的监听器发布启 动事件。

第八步:执行Runners。用于调用项目中自定义的执行器XxxRunner类,使得在项目启动完成后立即执行一些特定程序。其中, Spring Boot提供的执行器接口有ApplicationRunner 和CommandLineRunner两种,在使用时只需要 自定义一个执行器类实现其中一个接口并重写对应的run()方法接口,然后Spring Boot项目启动后会立 即执行这些特定程序 。

3、Spring Boot执行流程图

image.png