PS: Spring Boot 用的是 2.6.2 版本
自定义Starter
Spring Boot starter机制
SpringBoot是由众多Starter组成(一系列的自动化配置的starter插件),SpringBoot之所以流行也是因为starter。
starter是SpringBoot非常重要的一部分,可以理解为一个可插拔式的插件,正是这些starter使得使用某个功能的开发者不需要关注各种依赖库的处理,不需要具体的配置信息,由Spring Boot自动通过classpath路径下的类发现需要的Bean,并织入相应的Bean。
例如,想使用redis插件,那么可以使用spring-boot-starter-redis。
为什么要自定义starter
开发过程中,经常有一些独立于业务之外的配置模块。如果我们将这些可独立业务代码之外的功能配置模块封装成一个个starter,复用的时候只需要在pom引用依赖即可,SpringBoot为我们完成自动装配。
自定义starter的命名规则
SpringBoot提供的starter以spring-boot-starter-xxx的方式命名的。官方建议自定义的starter使用xx-spring-boot-starter命名规则。以区分SpringBoot生态提供的starter
整个过程分为两部分:
- 自定义starter
- 使用starter
首先先完成自定义Starter
- 新建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>
- 编写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;
}
}
- 编写配置类
@Configuration
//@ConditionalOnClass: 当类路径classpath下有指定的类的情况下进行自动配置
@ConditionalOnClass
public class MyAutoConfiguration {
static {
System.out.println("MyAutoConfiguration init ...");
}
@Bean
public SimpleBean simpleBean() {
return new SimpleBean();
}
}
-
resources下创建/META-INF/spring.factories
注意:META-INF是自己手动创建的目录,spring.factories也是手动创建的文件,在该文件中配置自己的自动配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.lagou.config.MyAutoConfiguration
使用自定义starter
- 导入自定义starter的依赖
<dependency>
<groupId>com.lagou</groupId>
<artifactId>zdy-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
- 在全局配置文件中配置属性值
simplebean.id=1
simplebean.name=starter
- 编写测试方法
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestBean {
@Autowired
SimpleBean simpleBean;
@Test
public void aa(){
System.out.println(simpleBean.getName());
}
}
执行原理
每个SpringBoot项目都有一个主程序启动类,在主程序启动类中有一个启动项目的main()方法,在该方法中通过执行SpringApplication.run()即可启动整个Spring Boot程序。
问题:那么SpringApplication.run()如何做到启动SpringBoot项目?
下面看一下run()方法内部的源码,核心代码如下
- 启动类
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
- SpringApplication#run
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启动项目,这两个阶段具体说明如下
a. 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 = Collections.emptySet();
this.isCustomEnvironment = false;
this.lazyInitialization = false;
this.applicationContextFactory = ApplicationContextFactory.DEFAULT;
this.applicationStartup = ApplicationStartup.DEFAULT;
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
//判断当前的webApplicationType
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
//设置初始化器,最后会调用这些初始化器
this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
//设置监听器
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
// 用于推断并设置项目main方法的主程序启动类
this.mainApplicationClass = this.deduceMainApplicationClass();
}
从上述源码可以看出,SpringApplication的初始化过程主要包括4部分,具体说明如下:
this.webApplicationType = WebApplicationType.deduceFromClasspath();
判断当前webApplicationType应用的类型。deduceFromClasspath方法用于查看Classpath类路径是否存在某个特征类,从而判断当前webApplicationType类型是SERVLET应用(Spring5之前的传统MVC应用)还是REACTIVE(Spring5开始出现的WebFlux交互式应用)this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
用于SpringApplication应用的初始化器配置。在初始化器设置过程中,会使用Spring类加载器SpringFactoriesLoader从META-INF/spring.factories类路径下的spriing.factories文件中获取所有可用的应用初始化器类ApplicationContextInitalizer。this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
用于SpringApplication应用的监听器设置。监听器设置的过程和上一步初始化器设置的过程基本一样,也是使用SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下spring.factories中获取所有可用的监听器类ApplicationListener。this.mainApplicationClass = this.deduceMainApplicationClass();
用于推断并设置项目main方法的主程序启动类
b. 项目的初始化启动
分析(new SpringApplication(primarySources)).run(args)源码前一部分SpringApplication实例对象的初始化创建后,查看run(args)方法执行的项目初始化启动过程,核心代码如下:
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
ConfigurableApplicationContext context = null;
this.configureHeadlessProperty();
//1. 获取并启动监听器
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//2. 根据SpringApplicationRunListeners以及参数来准备环境
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
this.configureIgnoreBeanInfo(environment);
// 准备Banner 打印器(就是启动SpringBoot 打印在控制台的ASCII艺术字体
Banner printedBanner = this.printBanner(environment);
//3. 创建Spring容器
context = this.createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
//4. Spring 容器前置处理
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//5. 刷新容器
this.refreshContext(context);
//6. Spring 容器后置处理
this.afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);
}
//7. 发出结束执行的事件
listeners.started(context, timeTakenToStartup);
//返回容器
this.callRunners(context, applicationArguments);
} catch (Throwable var12) {
this.handleRunFailure(context, var12, listeners);
throw new IllegalStateException(var12);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
return context;
} catch (Throwable var11) {
this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var11);
}
}
从上述源码可以看除,项目初始化启动过程大致包括以下部分:
- 获取并启动监听器
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
主要获取SpringApplication实例初始化过程中初始化的SpringApplicationRunListener监听器并运行
- 根据SpringApplicationRunListeners以及参数来准备环境
this.prepareEnvironment(listeners, bootstrapContext, 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方法,也就是说这里是在Spring容器中发布事件,并不是在SpringApplication中发布事件,和前面的starting是不同的,前面的starting是直接向SpringApplication中的监听器发布启动事件。
- 执行Runners
用于调用项目自定义执行器XxxRunner类,使得项目启动完成后立即执行一些特定程序。其中Sprng Boot提供的执行器接口有ApplicationRunner和CommandLineRunner两种,在使用时只需要自定义一个执行器类实现其中一个接口并重写对应的run方法接口,然后Spring Boot项目启动后会立即执行这些特定程序。
这边通过一个Spring Boot执行流程图,让大家更清晰的知道Spring Boot的整体执行流程和主要启动阶段:
fang