目 录CONTENT

文章目录

Java SPI机制介绍及原理分析

成培培
2025-01-10 / 0 评论 / 0 点赞 / 17 阅读 / 0 字

概念介绍

SPI 即 Service Provider Interface ,字面意思就是:“服务提供者的接口”,我的理解是专门给服务提供者使用的接口,也就是定义接口的人,和实现接口的人并不是同一个人
SPI 将服务接口和具体的服务实现分离开来,将服务调用方和服务实现者解耦,能够提升程序的扩展性、可维护性。修改或者替换服务实现并不需要修改调用方。
单看概念比较抽象,下面用演示代码具象一点看看到底是怎么回事

代码演示

SPI的核心是JDK中的这个类ServiceLoader,可以加载当前环境中指定接口的实现。
这里接口定义者定义了一个日志服务接口LogService,代码如下

public interface LogService {
    void info(String info);
}

该接口定义了一个输出日志的方法,并且提供了一个打印日志的函数

public static void printLog(String info) {
   ServiceLoader<LogService> serviceLoader = ServiceLoader.load(LogService.class);
   for (LogService logService : serviceLoader) {
       logService.info(info);
   }
}

这里就用到了ServiceLoader加载当前环境中LogService的实现,并且调用接口的info方法去打印日志,如果这是我们调用printLog将什么也不会打印,因为接口定义者只定义了接口,但是并没有实现接口,所以拿到的serviceLoader里并没有实现类,之所以接口定义者没有去实现接口是因为他并不知道使用printLog打印日志的调用方希望把日志打印到哪里去,可以打印到控制台,也可以打印到文件,也可以把日志信息通过网络发出去。
这里出现了一个服务提供者他会去实现接口,并且调用printLog打印日志实现他自己对日志输出位置的需求。
这时服务提供者需要做两件事件

  1. 实现接口定义者定义的接口
  2. 创建文件夹META-INF/services,其中放入以该接口的全类名为文件名的文件,文件第一行写入你的实现类的全类名

https://www.chengpei.top/upload/spi_demo.png
这时再调用printLog就会调用实现类实现需要的效果了
看看ServiceLoader的源码其实就会发现,他内部其实是读取了这个文件夹META-INF/services/下的文件内容,利用反射机制创建了接口的实现类对象。
以上演示代码提交到了github,https://github.com/chengpei/spi-demo

实际应用场景介绍

这里以hutool库中的模版引擎封装类TemplateUtil为例,介绍在实际应用场景中,他是如何利用SPI机制实现多种模版引擎的兼容。
通过一行代码可以创建一个模版引擎

TemplateEngine templateEngine = TemplateUtil.createEngine(new TemplateConfig());

但是模版引擎是有很多实现类的,如图:
https://www.chengpei.top/upload/spi_hutool.png
他是怎么确定要使用哪个实现类呢?这里其实如果我们在项目里添加了Freemarker的依赖的话,那么这里创建的就是FreemarkerEngine,如果项目中添加了Beetl的依赖,那些这里创建的就是BeetlEngine实现类,这里就是使用了SPI机制。
看源码这里是创建的文件,并且把所有的实现类都放进去了
https://www.chengpei.top/upload/spi_hutool_template.png
根据源码最后创建引擎实现类时调用了这个方法

public static <T> T loadFirstAvailable(Class<T> clazz) {
	final Iterator<T> iterator = load(clazz).iterator();
	while (iterator.hasNext()) {
		try {
			return iterator.next();
		} catch (ServiceConfigurationError ignore) {
			// ignore
		}
	}
	return null;
}

传进来的clazz当然是TemplateEngine.class接口,这里的load实际上就是调用的ServiceLoader.load(),这里循环会依次获取以上所有文件里的实现类,但是如果项目里没有beetl相关的依赖这里是会报错的,所以这里也捕获了异常什么也不做,继续找下一个实现类加载,因为的项目中有freemarker依赖所以加载FreemarkerEngine实现类没有报错,这里就直接返回不再继续遍历了,所以这个方法就是加载第一个可用的实现类
以上就是hutool库中,SPI机制在模版引擎工具类中的应用了,通过演示代码实操了解原理,再结合其他开源项目中的应用,可以更好的帮助我们理解及运用。

0

评论区