本文最后更新于2 分钟前,文中所描述的信息可能已发生改变。
本文分别介绍了 Java 中与 Spring 中提供的 SPI 机制,并且还介绍了如何使用 SpringBoot 的自动配置编写一个自己的 starter 依赖。
Java当中提供的SPI
在目录
resource/META-INF/services
下以接口全限定名作为文件名,文件内容为其实现类,一个文件对应一个接口。
文件名为Animals接口的全限定名com.ecjtu.api.Animals
文件内容为其实现类的全限定类名,一行一个。
txt
com.ecjtu.api.Cat
com.ecjtu.api.Dog
定义接口
java
public interface Animals {
void call();
}
定义实现类
java
//Cat类
public class Cat implements Animals {
@Override
public void call() {
System.out.println("我是猫:斯!");
}
}
//Dog类
public class Dog implements Animals {
@Override
public void call() {
System.out.println("我是狗:giao!");
}
}
Java提供的SPI使用ServiceLoader来加载对应接口的实现类
java
ServiceLoader<Animals> load = ServiceLoader.load(Animals.class);
for (Animals animals1 : load) {
animals1.call();
}
SpringBoot(Spring)提供的SPI
在目录
resource/META-INF
下创建文件名为spring.factories
的文件,文件内容为各个接口的全限定名及其实现类,以键值对的形式,一个文件中可以有多个接口及其多个实现类的定义。多个实现类之间以英文逗号分隔
spring.factories
文件内容,键是接口全限定名,值为其实现类的全限定名,逗号分隔多个实现类。
txt
com.ecjtu.api.Animals=\
com.ecjtu.api.Dog,\
com.ecjtu.api.Cat
使用SpringFactoriesLoader
来加载接口的实现类
java
List<Animals> animals = SpringFactoriesLoader.loadFactories(Animals.class, null);
for (Animals animal : animals) {
animal.call();
}
使用SpringBoot的自动配置机制实现一个自定义线程池的starter
需要的外部依赖
pom
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.7.17</version>
<optional>true</optional>
</dependency>
<!-- 可以不要,用来做参数校验的一个依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.7.17</version>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>*/**</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.17</version>
</plugin>
</plugins>
</build>
步骤
- 第一步:新建一个工程custom-thread-pool-spring-boot-starter。
- 第二步:创建自动配置类。
java
@Configuration
@ConditionalOnClass(ThreadPoolExecutor.class)//存在此类时才进行自动配置
@EnableConfigurationProperties({ThreadPoolProperties.class})//导入线程池属性配置类
public class ThreadPoolAutoConfiguration {
private static final Logger log = LoggerFactory.getLogger(ThreadPoolAutoConfiguration.class);
@Bean
public ThreadPoolExecutor threadPoolExecutor(@Autowired ThreadPoolProperties properties){
log.info("ThreadPoolExecutor --> 进行自动配置");
return new ThreadPoolExecutor(
properties.getCorePoolSize(),
properties.getMaximumPoolSize(),
properties.getKeepAliveTime(),
properties.getUnit(),
new ArrayBlockingQueue<>(properties.getWorkQueueSize())
);
}
}
- 第三步:创建
ThreadPoolProperties.class
用于从yml
文件中读取线程池初始化参数。
是通过
setter
方法进行的注入
java
@ConfigurationProperties("spring.custom.thread-pool")
public class ThreadPoolProperties {
/*
核心线程数
*/
@Range(min = 0,max = Integer.MAX_VALUE,message = "核心线程数量超出范围")
private int corePoolSize;
/*
最大线程数
*/
@Range(min = 1,max = Integer.MAX_VALUE,message = "最大线程数超出范围")
private int maximumPoolSize;
/*
存活时间
*/
@Range(min = 0l,max = Long.MAX_VALUE)
private long keepAliveTime;
/*
时间单位
*/
@NotNull
private TimeUnit unit;
/*
任务队列大小,默认任务队列为ArrayBlockingQueue
*/
@Range(min = 1,max = Integer.MAX_VALUE)
private int workQueueSize;
public ThreadPoolProperties() {
}
public int getCorePoolSize() {
return corePoolSize;
}
public void setCorePoolSize(int corePoolSize) {
this.corePoolSize = corePoolSize;
}
public int getMaximumPoolSize() {
return maximumPoolSize;
}
public void setMaximumPoolSize(int maximumPoolSize) {
this.maximumPoolSize = maximumPoolSize;
}
public long getKeepAliveTime() {
return keepAliveTime;
}
public void setKeepAliveTime(long keepAliveTime) {
this.keepAliveTime = keepAliveTime;
}
public TimeUnit getUnit() {
return unit;
}
public void setUnit(TimeUnit unit) {
this.unit = unit;
}
public int getWorkQueueSize() {
return workQueueSize;
}
public void setWorkQueueSize(int workQueueSize) {
this.workQueueSize = workQueueSize;
}
}
- 第四步:创建自定义类型转换器,将字符串类型转换为 TimeUnit 枚举类型。
java
@Component
@ConfigurationPropertiesBinding//声明注册此转换器
public class TimeUnitConverter implements Converter<String, TimeUnit> {
@Override
public TimeUnit convert(String source) {
TimeUnit t = null;
if(source.equals("SECONDS") || source.equals("seconds")){
t = TimeUnit.SECONDS;
}else if(source.equals("HOURS") || source.equals("hours")){
t = TimeUnit.HOURS;
}else if(source.equals("DAYS") || source.equals("days")){
t = TimeUnit.DAYS;
}else if(source.equals("MICROSECONDS") || source.equals("microseconds")){
t = TimeUnit.MICROSECONDS;
}else if(source.equals("MILLISECONDS") || source.equals("milliseconds")){
t = TimeUnit.MILLISECONDS;
}else if(source.equals("NANOSECONDS") || source.equals("nanoseconds")){
t = TimeUnit.NANOSECONDS;
}else if(source.equals("MINUTES") || source.equals("minutes")){
t = TimeUnit.MINUTES;
}
return t;
}
}
- 第五步:新建文件
resource/META-INF/spring.factories
文件内容
xml
# 指定自定义线程池的自动配置类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ecjtu.autoconfigure.ThreadPoolAutoConfiguration
至此已经完成,在需要使用此线程池的地方导入该项目依赖,并在yml文件中配置相应参数即可使用SpringBoot的自动注入功能注入此线程池对象。
yml
# 配置示例
spring:
custom:
thread-pool:
corePoolSize: 20
maximumPoolSize: 20
keepAliveTime: 20
unit: SECONDS
workQueueSize: 5
小结
Spring提供的SPI比Java提供的更加方便,不需要多个文件,一个文件即可定义全部接口及其实现类。都可以实现根据定义的bean信息并实例化。SpringBoot自动配置也是通过扫描spring.factories
文件中org.springframework.boot.autoconfigure.EnableAutoConfiguration
的值来注入自动配置类进行自动配置(自动加载配置类)。