详解Spring的SPI机制和自定义Springboot的Starter包
为了给组里的实习生妹妹讲如何自定义Springboot的Starter,
我觉得需要让她先知道Springboot的自动装配机制,
但又感觉是不是得先让她了解Spring的SPI机制,
最后又觉得还有必要说明一下一个@Configuration配置类在Spring中如何被解析,这又会引出对ConfigurationClassPostProcessor的讲解,
完了,收不住了,到处都是知识盲区。
知识盲区脑图如下。
往期文章
一文学会Spring的@Configuration配置类解析
前言
本文会先对JDK的SPI机制和Spring的SPI机制进行说明和演示,最后演示如何自定义Springboot的starter。
Springboot版本:2.4.1
正文
一. JDK-SPI机制
SPI全称为Service Provider Interface,是指在模块装配的时候动态加载接口的实现类的一种机制。使用JDK-SPI的规则如下所示。
- 服务提供模块提供了接口的具体实现后,需要在服务提供模块的jar包的META-INF/services目录下创建一个配置文件,配置文件名为接口的全限定名,文件内容为当前jar包中接口的实现类的全限定名;
- 服务提供模块的jar包需要在主程序的classpath下;
- 在主程序中,通过java.util.ServiceLoader的load(接口.class) 方法来加载接口的实现类,实现方式就是遍历classpath下每个jar包中的META-INF/services目录下的配置文件,通过配置文件中的接口实现类的全限定名加载接口实现类;
- 接口实现类必须有无参构造方法。
下面是一个具体示例。
定义接口和实现类,如下所示。
public interface MyApi {}
public class MyController implements MyApi {}
public class MyService implements MyApi {}
public class MyDao implements MyApi {}
在META-INF/services目录下创建配置文件,配置文件名为com.learn.spi.jdkspi.MyApi
,内容如下所示。
com.learn.spi.jdkspi.MyController
com.learn.spi.jdkspi.MyService
com.learn.spi.jdkspi.MyDao
测试程序如下所示。
public class MyTest {
public static void main(String[] args) {
ServiceLoader<MyApi> myApis = ServiceLoader.load(MyApi.class);
for (MyApi myApi : myApis) {
System.out.println(myApi.getClass().getName());
}
}
}
测试结果如下所示。
二. Spring-SPI机制
Spring中提供了和JDK-SPI类似的机制,通过SpringFactoriesLoader扫描classpath下所有jar包中META-INF目录下spring.factories文件,来得到需要加载的类的集合。
下面是一个具体示例。
定义两个接口,如下所示。
public interface MyApi {}
public interface MyAnotherApi {}
定义业务类,如下所示。
public class MyController {}
public class MyService {}
public class MyDao {}
public class MyAnotherController implements MyAnotherApi {}
public class MyAnotherService implements MyAnotherApi {}
public class MyAnotherDao implements MyAnotherApi {}
spring.factories文件内容如下所示。
com.learn.spi.springspi.MyApi=\
com.learn.spi.springspi.MyController,\
com.learn.spi.springspi.MyService,\
com.learn.spi.springspi.MyDao
com.learn.spi.springspi.MyAnotherApi=\
com.learn.spi.springspi.MyAnotherController,\
com.learn.spi.springspi.MyAnotherService,\
com.learn.spi.springspi.MyAnotherDao
测试程序如下所示。
public class MyTest {
public static void main(String[] args) {
// 不要求加载的类实现了接口
List<String> factoryNames = SpringFactoriesLoader
.loadFactoryNames(MyApi.class, null);
for (String factoryName : factoryNames) {
System.out.println(factoryName);
}
// 要求加载的类实现了接口
List<MyAnotherApi> myAnotherApis = SpringFactoriesLoader
.loadFactories(MyAnotherApi.class, null);
for (MyAnotherApi myAnotherApi : myAnotherApis) {
System.out.println(myAnotherApi.getClass().getName());
}
}
}
测试结果如下所示。
三. 自定义starter
相信有了前面几篇文章的铺垫,现在自己实现一个starter,简直是信手拈来。
自定义starter组件工程结构如下所示。
MyModuleController,MyModuleService和MyModuleDao定义如下。
public class MyModuleController {}
public class MyModuleService {}
public class MyModuleDao {}
MyModuleConfig定义如下。
@Configuration
public class MyModuleConfig {
@Bean
public MyModuleController myModuleController() {
return new MyModuleController();
}
@Bean
public MyModuleService myModuleService() {
return new MyModuleService();
}
@Bean
public MyModuleDao myModuleDao() {
return new MyModuleDao();
}
}
spring.factories内容如下。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.learn.module.stater.config.MyModuleConfig
POM文件内容如下。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.4.1</version>
</parent>
<groupId>com.learn.module.stater</groupId>
<artifactId>learn-module-starter</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
最后通过Maven将自定义starter组件安装到本地仓库。
四. 测试自定义starter
创建一个测试工程,工程目录如下所示。
POM文件内容如下。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.4.1</version>
</parent>
<groupId>com.test.module.starter</groupId>
<artifactId>test-module-starter</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.learn.module.stater</groupId>
<artifactId>learn-module-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
ModuleStarterApplication定义如下。
@SpringBootApplication
public class ModuleStarterApplication {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext
= SpringApplication.run(ModuleStarterApplication.class, args);
MyModuleController myModuleController = applicationContext
.getBean(MyModuleController.class);
MyModuleService myModuleService = applicationContext
.getBean(MyModuleService.class);
MyModuleDao myModuleDao = applicationContext
.getBean(MyModuleDao.class);
System.out.println(myModuleController);
System.out.println(myModuleService);
System.out.println(myModuleDao);
}
}
运行ModuleStarterApplication,打印如下所示。
总结
至此,总算是能够给组里的实习生妹妹讲明白如何自定义一个starter。当然,自定义starter是很简单的,但是其背后的工作原理,还是有一定的复杂度的。
比如我们的starter里面的各种bean,如何被Springboot加载到容器,这里面就涉及到Spring的SPI机制以及Springboot的自动装配机制。
而Spring对@Configuration配置类的解析看起来和Springboot的自动装配机制没什么关系,但是却又是息息相关。
所以如果都明白了上述的一些机制的作用原理,那么实现一个starter,真的就是很简单的事情了。
如果觉得本篇文章对你有帮助,求求你点个赞,加个收藏最后再点个关注吧。创作不易,感谢支持!
转载自:https://juejin.cn/post/7212960463730556983