个人博客:无奈何杨(wnhyang)
个人语雀:wnhyang
共享语雀:在线知识共享
Github:wnhyang - Overview
Java SPI
简介
以下引用AI
。
Java SPI
(Service Provider Interface
)是一种用于实现组件化的机制,它允许软件组件以插件的形式被发现和加载。在 Java
中,SPI
是通过在META-INF/services
目录下的特定文件中声明服务提供者来实现的。
下面是 Java SPI
的基本工作原理:
- 定义服务接口:首先,定义一个服务接口,该接口规定了一组方法或功能。
- 实现服务提供者:然后,编写一个或多个实现了服务接口的具体类,这些类被称为服务提供者。
- 创建服务配置文件:在
META-INF/services
目录下创建一个以服务接口全限定名命名的文件,其中包含了实现了服务接口的具体类的全限定名。 - 加载服务提供者:通过
Java SPI
机制,Java
运行时会自动加载并实例化服务提供者,并将其注入到应用程序中,使得我们可以在运行时动态地发现和使用这些服务。
LiteFlow
的SPI
实践
对于现在常见的各种spring-boot-starter
通常都会有以下结构,xxx-core
、xxx-autoconfigure
、spring-boot-autoconfiguare
等等。
没错!就是自动装配,这也是我们使用Spring Boot
最重要的原因之一。自动装配极大的简化了我们开发中的配置,从学习Spring
到现在的Spring Boot
我还是不敢讲我对此思想有怎样深刻的认识,包括现在,但我好像又确定有了一些理解,有点体会到这些思想的奥妙,就像当时大学时看着高数老师一点点推算微积分公式一样,有些着迷。
回归正题。自动装配的原理可以自己去学习。
各种框架为了占领市场,会推出各种框架的适配版本或是插件,都是为了加大影响力,为了证明自己的框架确实牛,什么环境都可以使用。
所以从他们的源码项目中,都会有各种插件包,如xxx-servlet
、xxx-spring
、xxx-solon
、xxx-dubbo
等等。
如上是LiteFlow
的源码项目,直接看liteflow-core
下的spi
包。
其中SpiPriority
为基础接口,通过mac
快捷键control + H
(windows
为ctrl + H
)查看其继承树,发现最终归结于三个类,LocalXXX
、SpringXXX
、SolonXXX
,他们就是LiteFlow
针对不同环境的SPI
实现类。
SpiPriority接口
java
/**
* Spi实现的优先级接口 数字越小优先级越高
*
* @author Bryan.Zhang
* @since 2.6.11
*/
public interface SpiPriority {
int priority();
}
priority
接口在后面Holder
中有用。
所有实现方法都一样,所以举一个例子就够了。如下,是组件全局切面spi
接口,他有三个实现,分别对应三个不同环境。
SpringCmpAroundAspect
Spring
环境的实现类,我贴在这了,其他可以自行查看源码。
三个不同的实现类,都重写了priority
方法,分别对应返回2(local
)、1(spring
)、1(solon
)。
java
/**
* Spring环境全局组件切面实现
*
* @author Bryan.Zhang
* @since 2.6.11
*/
public class SpringCmpAroundAspect implements CmpAroundAspect {
@Override
public void beforeProcess(NodeComponent cmp) {
if (ObjectUtil.isNotNull(ComponentScanner.cmpAroundAspect)) {
ComponentScanner.cmpAroundAspect.beforeProcess(cmp);
}
}
@Override
public void afterProcess(NodeComponent cmp) {
if (ObjectUtil.isNotNull(ComponentScanner.cmpAroundAspect)) {
ComponentScanner.cmpAroundAspect.afterProcess(cmp);
}
}
@Override
public void onSuccess(NodeComponent cmp) {
if (ObjectUtil.isNotNull(ComponentScanner.cmpAroundAspect)) {
ComponentScanner.cmpAroundAspect.onSuccess(cmp);
}
}
@Override
public void onError(NodeComponent cmp, Exception e) {
if (ObjectUtil.isNotNull(ComponentScanner.cmpAroundAspect)) {
ComponentScanner.cmpAroundAspect.onError(cmp, e);
}
}
@Override
public int priority() {
return 1;
}
}
实现SPI接口后需要在META-INF/services
下声明。
CmpAroundAspectHolder
如何获取对应环境下的全局拦截器呢?
靠的就是CmpAroundAspectHolder
,如下。
ServiceLoader.load
用于加载SPI
实现类,然后排序,获取首个作为当前环境的实例。
csharp
/**
* 组件全局拦截器SPI工厂类
*
* @author Bryan.Zhang
* @since 2.6.11
*/
public class CmpAroundAspectHolder {
private static CmpAroundAspect cmpAroundAspect;
public static CmpAroundAspect loadCmpAroundAspect() {
if (ObjectUtil.isNull(cmpAroundAspect)) {
List<CmpAroundAspect> list = new ArrayList<>();
ServiceLoader.load(CmpAroundAspect.class).forEach(list::add);
list.sort(Comparator.comparingInt(CmpAroundAspect::priority));
cmpAroundAspect = list.get(0);
}
return cmpAroundAspect;
}
public static void clean() {
cmpAroundAspect = null;
}
}
啊?这样的吗?
以下两个标题内容引用AI
。
SPI的优缺点
上从上面看spring
和solon
的优先级都为1,本身这就是不同的环境,应当不存在同时依赖的情况的!?
应该吧!
优点:
- 松耦合:SPI将服务接口和具体实现解耦,使得应用程序不需要硬编码特定的实现类,从而提高了代码的灵活性和可维护性。
- 模块化:通过SPI,应用程序可以轻松地添加、删除或替换服务的实现,而无需修改现有的代码,从而实现了模块化和可插拔的设计。
- 可扩展性:SPI允许在运行时动态地发现和加载服务的实现,使得应用程序可以在不重新编译或重新部署的情况下扩展功能。
- 标准化:SPI是Java标准库中的一部分,因此遵循了一定的标准和约定,使得开发者可以按照统一的方式实现和使用服务提供者。
缺点:
- 发现机制的限制:SPI的发现机制是基于Java的类加载机制的,它要求服务提供者的实现类必须被放置在特定的位置,并且需要符合一定的命名约定,这限制了一些灵活性和自由度。
- 单一实现:SPI的标准机制只能支持单一的实现,如果一个服务接口有多个实现类,就需要额外的手段来处理,比如自定义解析配置文件等。
- 可见性和权限:在某些情况下,由于类加载器的限制,SPI可能无法访问到某些服务提供者的实现类,导致无法正确加载服务。
- 运行时性能开销:SPI的动态发现和加载机制可能会引入一定的运行时性能开销,尤其是在服务提供者数量较多的情况下。
SPI
与Spring
对比
SPI
(Service Provider Interface
)是Java
中的一种设计模式,用于实现模块化的可扩展性。它允许定义服务接口,而不关心具体的实现,然后通过在运行时发现和加载服务的实现来扩展应用程序的功能。SPI
的核心思想是将服务的定义和实现解耦,使得应用程序可以在不修改代码的情况下轻松地添加新的服务实现。
Spring
框架中的思想与SPI
有一些相似之处,尤其是在扩展和定制方面。Spring
提供了许多扩展点和插件机制,允许开发人员通过实现特定的接口或继承特定的类来定制和扩展框架的功能。
总的来说,SPI
和Spring
框架都倡导松耦合、模块化和可扩展性,通过定义接口和提供扩展点来实现这些目标。因此,可以说它们在思想上是相近的。
写在最后
拙作艰辛,字句心血,望诸君垂青,多予支持,不胜感激。
个人博客:无奈何杨(wnhyang)
个人语雀:wnhyang
共享语雀:在线知识共享
Github:wnhyang - Overview