0%

Spring-SPI

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。

  • 本文对比JDK、Spring对SPI机制的实现。

  • 在实际的应用场景。

    • jdbc驱动,不同的数据有不同的驱动。

    com.mysql.cj.jdbc.Driver

    • spring 也使用spi机制,用于扩展实现。

    org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory

    • dubbo也通过spi机制实现自定义扩展。
    • 日志门面接口实现类加载,SLF4J加载不同提供商的日志是现类。

SPI

演示代码

JDK 中 提供了一个 SPI 的功能,核心类是 java.util.ServiceLoader。其作用就是,可以通过类名获取在”META-INF/services/“下的多个配置实现文件。

ServiceLoader

  • META-INF/services下的文件(以Interface全路径命名)中添加具体实现类的全路径名。
  • 使用程序使用ServiceLoader动态加载实现类(根据目录META-INF/services下的配置文件找到实现类的全限定名并调用classloader来加载实现类到JVM)
  • SPI的实现类必须具有无参数的构造方法

演示代码

目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
└── src
├── main
│   ├── java
│   │   └── cn
│   │   └── z201
│   │   └── spi
│   │   ├── SpeakChinese.java
│   │   ├── SpeakEnglish.java
│   │   ├── SpeakGerman.java
│   │   ├── SpeakSPIService.java // 定义接口
│   │   └── package-info.java
│   └── resources
│   └── META-INF
│   └── services
│   └── cn.z201.spi.SpeakSPIService // 配置文件,文件名以接口包和类名称组成。
接口
1
2
3
public interface SpeakSPIService {
Object echo();
}
实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SpeakGerman implements SpeakSPIService{

@Override
public Object echo() {
return "german : hallo";
}
}

public class SpeakEnglish implements SpeakSPIService{
@Override
public Object echo() {
return "english : hello";
}
}

public class SpeakChinese implements SpeakSPIService {

@Override
public Object echo() {
return "chinese : 你好";
}
}
配置文件
  • SpeakSPIService就是接口的名称
1
2
3
cn.z201.spi.SpeakChinese
cn.z201.spi.SpeakGerman
cn.z201.spi.SpeakEnglish
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
class SpeakSPIServiceTest {

@Test
public void setUp(){
ServiceLoader<SpeakSPIService> serviceLoader = ServiceLoader.load(SpeakSPIService.class, Thread.currentThread().getContextClassLoader());
Iterator<SpeakSPIService> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
SpeakSPIService speakSPIService = iterator.next();
System.out.println("speakSPIService " + speakSPIService.echo());
}
}

}
  • Console
1
2
3
4
speakSPIService chinese : 你好
speakSPIService german : hallo
speakSPIService english : hello
# 实际工作中这么用肯定太难用了,这里小改动一下。
改进
  • 使用注解标注类型,通过注解信息获取对应的实现类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface LanguageType {

String language();
}

@LanguageType(language = "chinese")
public class SpeakChinese implements SpeakSPIService {

@Override
public Object echo() {
return "chinese : 你好";
}
}
@LanguageType(language = "english")
public class SpeakEnglish implements SpeakSPIService{
@Override
public Object echo() {
return "english : hello";
}
}
@LanguageType(language = "german")
public class SpeakGerman implements SpeakSPIService{

@Override
public Object echo() {
return "german : hallo";
}
}

// 工具类,根据类注解反射获取数据
public class LanguageTypeTool {

public static SpeakSPIService language(String language){
ServiceLoader<SpeakSPIService> serviceLoader = ServiceLoader.load(SpeakSPIService.class, Thread.currentThread().getContextClassLoader());
Iterator<SpeakSPIService> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
SpeakSPIService speakSPIService = iterator.next();
Class clazz = speakSPIService.getClass();
LanguageType languageType = (LanguageType) clazz.getDeclaredAnnotation(LanguageType.class);
if (null != languageType) {
if (Objects.equals(language,languageType.language())) {
return speakSPIService;
}
}
}
return null;
}
}
  • 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Test
public void setUp() {
ServiceLoader<SpeakSPIService> serviceLoader = ServiceLoader.load(SpeakSPIService.class, Thread.currentThread().getContextClassLoader());
Iterator<SpeakSPIService> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
SpeakSPIService speakSPIService = iterator.next();
System.out.println(speakSPIService.echo());
}
SpeakSPIService speakSPIService = LanguageTypeTool.language("chinese");
if (null != speakSPIService) {
System.out.println(speakSPIService.echo());
}
speakSPIService = LanguageTypeTool.language("english");
if (null != speakSPIService) {
System.out.println(speakSPIService.echo());
}
}
  • Console
1
2
3
4
5
chinese : 你好
german : hallo
english : hello
chinese : 你好
english : hello

ServiceLoaderFactoryBean

通过Java提供的SPI的方式来加载相关实现类并注册到Spring容器中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ServiceLoaderFactoryBean extends AbstractServiceLoaderBasedFactoryBean implements BeanClassLoaderAware {

@Override
protected Object getObjectToExpose(ServiceLoader<?> serviceLoader) {
return serviceLoader;
}

// 这里引用的就是jdk中的ServiceLoader
@Override
public Class<?> getObjectType() {
return ServiceLoader.class;
}

}
演示代码
  • 项目引用JDK-SPI的代码
1
2
3
4
5
<dependency>
<groupId>cn.z201.spi</groupId>
<artifactId>Jdk-SPI</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @author z201.coding@gmail.com
**/
@Configuration
public class SpeakSPIServiceConfig {

// 指定bean的id ,通过依赖查找或者依赖注入可以找到。
@Bean("initBeanFactoryServiceLoaderFactoryBean")
public ServiceLoaderFactoryBean serviceLoaderFactoryBean() {
ServiceLoaderFactoryBean serviceLoaderFactoryBean = new ServiceLoaderFactoryBean();
serviceLoaderFactoryBean.setServiceType(SpeakSPIService.class);
return serviceLoaderFactoryBean;
}

}
测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void setSpringUp() {
// 创建BeanFactory 容器
AnnotationConfigApplicationContext config = new AnnotationConfigApplicationContext();
config.register(SpeakSPIServiceConfig.class);
// 启动上下文
config.refresh();
ServiceLoader<SpeakSPIService> serviceLoader = config.getBean("initBeanFactoryServiceLoaderFactoryBean", ServiceLoader.class);
displayServiceLoader(serviceLoader);
// 关闭上下文
config.close();
}

private void displayServiceLoader(ServiceLoader<SpeakSPIService> serviceLoader){
Iterator<SpeakSPIService> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
SpeakSPIService speakSPIService = iterator.next();
System.out.println("speakSPIService " + speakSPIService.echo());
}
}
  • Console
1
2
3
4
speakSPIService chinese : 你好
speakSPIService german : hallo
speakSPIService english : hello
# 既然用到了spring 当然不会这么简单就结束。
改进
  • 接口实现类注册成bean。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Test
public void setSpringRegisterBeanUp() {
// 创建BeanFactory 容器
AnnotationConfigApplicationContext config = new AnnotationConfigApplicationContext();
config.register(SpeakSPIServiceConfig.class);
// 启动上下文
config.refresh();
registerBean(config);
lookupCollectionType(config);
// 关闭上下文
config.close();
}


private void registerBean(AnnotationConfigApplicationContext applicationContext){
ServiceLoader<SpeakSPIService> serviceLoader = applicationContext.getBean("initBeanFactoryServiceLoaderFactoryBean", ServiceLoader.class);
Iterator<SpeakSPIService> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
SpeakSPIService speakSPIService = iterator.next();
Class clazz = speakSPIService.getClass();
LanguageType languageType = (LanguageType) clazz.getDeclaredAnnotation(LanguageType.class);
if (null != languageType) {
applicationContext.registerBean(languageType.language(),clazz);
}
}
}

/**
* 单一类型集合查找
*
* @param beanFactory
*/
private void lookupCollectionType(BeanFactory beanFactory) {
if (beanFactory instanceof ListableBeanFactory) {
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) beanFactory;
Map<String, SpeakSPIService> beansMap = listableBeanFactory.getBeansOfType(SpeakSPIService.class);
// getBeansOfType 匹配所有类型的 bean,无论是单例、原型还是 FactoryBean , bean name 作为key value 作为对象
beansMap.forEach((key, value) -> {
System.out.println("单一类型集合查找 " + key + " " + value.echo());
});
}
}
  • Console
1
2
3
单一类型集合查找  chinese chinese : 你好
单一类型集合查找 german german : hallo
单一类型集合查找 english english : hello

END