乐趣区

SpringBoot基础回顾9

SpringBoot starter 机制

​ SpringBoot 由众多 Starter 组成(一系列的自动化配置的 starter 插件),SpringBoot 之所以流行,也是因为 starter。

starter 是 SpringBoot 非常重要的一部分,可以理解为一个可拔插式的插件,正是这些 starter 使得使用某个功能的开发者不需要关注各种依赖库的处理,不需要具体的配置信息,由 Spring Boot 自动通过 classpath 路径下的类发现需要的 Bean,并织入相应的 Bean。

例如,你想使用 Reids 插件,那么可以使用 spring-boot-starter-redis;如果想使用 MongoDB,可以使用 spring-boot-starter-data-mongodb

为什么要自定义 starter

开发过程中,经常会有一些独立于业务之外的配置模块。如果我们将这些可独立于业务代码之外的功能配置模块封装成一个个 starter,复用的时候只需要将其在 pom 中引用依赖即可,SpringBoot 为我们完成自动装配

自定义 starter 的命名规则

SpringBoot 提供的 starter 以 spring-boot-starter-xxx 的方式命名的。官方建议自定义的 starter 使用 xxx-spring-boot-starter 命名规则。以区分 SpringBoot 生态提供的 starter

整个过程分为两部分:

  • 自定义 starter
  • 使用 starter

首先,先完成自定义 starter

(1)新建 maven jar 工程,工程名为 zdy-spring-boot-starter,导入依赖:


<dependencies>

   
<dependency>

       
<groupId>org.springframework.boot</groupId>

       
<artifactId>spring-boot-autoconfigure</artifactId>

       
<version>2.2.2.RELEASE</version>

   
</dependency>

</dependencies>

(2)编写 javaBean


@EnableConfigurationProperties(SimpleBean.class)


@ConfigurationProperties(prefix =
"simplebean") 

public class SimpleBean {

 

   
private int id;

   
private String name;

 

   
public int getId() {return id;}

 

   
public void setId(int id) {this.id = id;}

 

   
public String getName() {return name;}

 

   
public void setName(String name) {this.name = name;}

 

   
@Override

   
public String toString() {

       
return "SimpleBean{" +

                "id=" + id +

                ", name='" + name +
'\'' +

                '}';

    }

}

(3)编写配置类 MyAutoConfiguration


@Configuration

@ConditionalOnClass //@ConditionalOnClass:当类路径 classpath 下有指定的类的情况下进行自动配置

public class MyAutoConfiguration {

 

 

   
static {System.out.println("MyAutoConfiguration init....");

    }

 

 

   
@Bean

   
public SimpleBean simpleBean(){return new SimpleBean();

    }

 

}

(4)resources 下创建 /META-INF/spring.factories

注意:META-INF 是自己手动创建的目录,spring.factories 也是手动创建的文件, 在该文件中配置自己的自动配置类

<img
src=”./images/image-20200111123116471.png”
alt=”image-20200111123116471″ style=”zoom:67%;” />


org.springframework.boot.autoconfigure.EnableAutoConfiguration=\

com.lagou.config.MyAutoConfiguration

使用自定义 starter

(1)导入自定义 starter 的依赖


<dependency>

  
<groupId>com.lagou</groupId>

  
<artifactId>zdy-spring-boot-starter</artifactId>

  
<version>1.0-SNAPSHOT</version>

 

</dependency>

(2)在全局配置文件中配置属性值


simplebean.id=1

simplebean.name= 自定义 starter

(3)编写测试方法


// 测试自定义 starter

@Autowired

private SimpleBean simpleBean;

 

@Test

public void zdyStarterTest(){System.out.println(simpleBean);

}

2.4 执行原理


每个 Spring Boot 项目都有一个主程序启动类,在主程序启动类中有一个启动项目的 main()方法,在该方法中通过执行 SpringApplication.run()即可启动整个 Spring Boot 程序。

问题:那么 SpringApplication.run()方法到底是如何做到启动 Spring Boot 项目的呢?

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


@SpringBootApplication

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

   }

 

}

public static
ConfigurableApplicationContext run(Class<?> primarySource, 

String... args) {

       return
run(new Class[]{primarySource}, args);

}

public static ConfigurableApplicationContext
run(Class<?>[] primarySources, 

String[] args) {

       return
(new SpringApplication(primarySources)).run(args);

}


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

1.SpringApplication 实例的初始化创建


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


public SpringApplication(ResourceLoader
resourceLoader, Class... primarySources) {

       this.sources
= new LinkedHashSet();

       this.bannerMode
= Mode.CONSOLE;

       this.logStartupInfo
= true;

       this.addCommandLineProperties
= true;

       this.addConversionService
= true;

       this.headless
= true;

       this.registerShutdownHook
= true;

       this.additionalProfiles
= new HashSet();

       this.isCustomEnvironment
= false;

       this.resourceLoader
= resourceLoader;

       Assert.notNull(primarySources,
"PrimarySources must not be null");

  // 把项目启动类.class 设置为属性存储起来

       this.primarySources
= new LinkedHashSet(Arrays.asList(primarySources));

  

  // 判断当前 webApplicationType 应用的类型

       this.webApplicationType
= WebApplicationType.deduceFromClasspath();

  

  // 设置初始化器(Initializer), 最后会调用这些初始化器

       this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));

  // 设置监听器(Listener)

       this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));

  // 用于推断并设置项目 main()方法启动的主程序启动类

       this.mainApplicationClass
= this.deduceMainApplicationClass();

 

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

(1)this.webApplicationType
= WebApplicationType.deduceFromClasspath()

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

(2)this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class))

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

(3)this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class))

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

(4)this.mainApplicationClass
= this.deduceMainApplicationClass()

用于推断并设置项目 main()方法启动的主程序启动类

2.项目的初始化启动


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


public ConfigurableApplicationContext
run(String... args) {

       StopWatch
stopWatch = new StopWatch();

       stopWatch.start();

       ConfigurableApplicationContext
context = null;

       Collection<SpringBootExceptionReporter>
exceptionReporters = new ArrayList();

       this.configureHeadlessProperty();

    
// 第一步:获取并启动监听器

       SpringApplicationRunListeners
listeners = this.getRunListeners(args);

       listeners.starting();

       Collection
exceptionReporters;

       try
{

              ApplicationArguments
applicationArguments = 

new DefaultApplicationArguments(args);

   
// 第二步:根据 SpringApplicationRunListeners 以及参数来准备环境

              ConfigurableEnvironment
environment = 

this.prepareEnvironment(listeners,
applicationArguments);

              this.configureIgnoreBeanInfo(environment);

    
// 准备 Banner 打印器 - 就是启动 Spring Boot 的时候打印在 console 上的 ASCII 艺术字体

              Banner
printedBanner = this.printBanner(environment);

    

              //
第三步:创建 Spring 容器

              context
= this.createApplicationContext();

              exceptionReporters
=

                     this.getSpringFactoriesInstances(SpringBootExceptionReporter.class,


new
Class[]{ConfigurableApplicationContext.class}, new Object[]{context});

    

   
// 第四步:Spring 容器前置处理

this.prepareContext(context, environment,
listeners, 

applicationArguments, printedBanner);

    

    
// 第五步:刷新容器

              this.refreshContext(context);

    

    
// 第六步:Spring 容器后置处理

              this.afterRefresh(context,
applicationArguments);

              stopWatch.stop();

              if(this.logStartupInfo)
{

                     (new
StartupInfoLogger(this.mainApplicationClass))

.logStarted(this.getApplicationLog(),
stopWatch);

              }

   
// 第七步:发出结束执行的事件

              listeners.started(context);

    

    
// 返回容器

              this.callRunners(context,
applicationArguments);

       }
catch (Throwable var10) {

              this.handleRunFailure(context,
var10, exceptionReporters, listeners);

              throw
new IllegalStateException(var10);

       }

       try
{listeners.running(context);

              return
context;

       }
catch (Throwable var9) {

              this.handleRunFailure(context,
var9, exceptionReporters,

 (SpringApplicationRunListeners)null);

              throw
new IllegalStateException(var9);

       }

}

 

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

  • 第一步:获取并启动监听器

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 项目启动后会立即执行这些特定程序

上了拉勾教育的《Java 工程师高薪训练营》,做一下笔记。希望拉勾能给我推到想去的公司,目标:字节!!

退出移动版