从LiteFlow来看Java SPI,与Spring框架有哪些相同的思想

个人博客:无奈何杨(wnhyang)

个人语雀:wnhyang

共享语雀:在线知识共享

Github:wnhyang - Overview


Java SPI简介

以下引用AI

Java SPIService Provider Interface)是一种用于实现组件化的机制,它允许软件组件以插件的形式被发现和加载。在 Java 中,SPI 是通过在META-INF/services目录下的特定文件中声明服务提供者来实现的。

下面是 Java SPI 的基本工作原理:

  1. 定义服务接口:首先,定义一个服务接口,该接口规定了一组方法或功能。
  2. 实现服务提供者:然后,编写一个或多个实现了服务接口的具体类,这些类被称为服务提供者。
  3. 创建服务配置文件:在 META-INF/services 目录下创建一个以服务接口全限定名命名的文件,其中包含了实现了服务接口的具体类的全限定名。
  4. 加载服务提供者:通过 Java SPI 机制,Java 运行时会自动加载并实例化服务提供者,并将其注入到应用程序中,使得我们可以在运行时动态地发现和使用这些服务。

LiteFlowSPI实践

对于现在常见的各种spring-boot-starter通常都会有以下结构,xxx-corexxx-autoconfigurespring-boot-autoconfiguare等等。

没错!就是自动装配,这也是我们使用Spring Boot最重要的原因之一。自动装配极大的简化了我们开发中的配置,从学习Spring到现在的Spring Boot我还是不敢讲我对此思想有怎样深刻的认识,包括现在,但我好像又确定有了一些理解,有点体会到这些思想的奥妙,就像当时大学时看着高数老师一点点推算微积分公式一样,有些着迷。

回归正题。自动装配的原理可以自己去学习。

各种框架为了占领市场,会推出各种框架的适配版本或是插件,都是为了加大影响力,为了证明自己的框架确实牛,什么环境都可以使用。

所以从他们的源码项目中,都会有各种插件包,如xxx-servletxxx-springxxx-solonxxx-dubbo等等。

如上是LiteFlow的源码项目,直接看liteflow-core下的spi包。

其中SpiPriority为基础接口,通过mac快捷键control + Hwindowsctrl + H)查看其继承树,发现最终归结于三个类,LocalXXXSpringXXXSolonXXX,他们就是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的优缺点

上从上面看springsolon的优先级都为1,本身这就是不同的环境,应当不存在同时依赖的情况的!?

应该吧!

优点:

  1. 松耦合:SPI将服务接口和具体实现解耦,使得应用程序不需要硬编码特定的实现类,从而提高了代码的灵活性和可维护性。
  2. 模块化:通过SPI,应用程序可以轻松地添加、删除或替换服务的实现,而无需修改现有的代码,从而实现了模块化和可插拔的设计。
  3. 可扩展性:SPI允许在运行时动态地发现和加载服务的实现,使得应用程序可以在不重新编译或重新部署的情况下扩展功能。
  4. 标准化:SPI是Java标准库中的一部分,因此遵循了一定的标准和约定,使得开发者可以按照统一的方式实现和使用服务提供者。

缺点:

  1. 发现机制的限制:SPI的发现机制是基于Java的类加载机制的,它要求服务提供者的实现类必须被放置在特定的位置,并且需要符合一定的命名约定,这限制了一些灵活性和自由度。
  2. 单一实现:SPI的标准机制只能支持单一的实现,如果一个服务接口有多个实现类,就需要额外的手段来处理,比如自定义解析配置文件等。
  3. 可见性和权限:在某些情况下,由于类加载器的限制,SPI可能无法访问到某些服务提供者的实现类,导致无法正确加载服务。
  4. 运行时性能开销:SPI的动态发现和加载机制可能会引入一定的运行时性能开销,尤其是在服务提供者数量较多的情况下。

SPISpring对比

SPIService Provider Interface)是Java中的一种设计模式,用于实现模块化的可扩展性。它允许定义服务接口,而不关心具体的实现,然后通过在运行时发现和加载服务的实现来扩展应用程序的功能。SPI 的核心思想是将服务的定义和实现解耦,使得应用程序可以在不修改代码的情况下轻松地添加新的服务实现。

Spring框架中的思想与SPI有一些相似之处,尤其是在扩展和定制方面。Spring提供了许多扩展点和插件机制,允许开发人员通过实现特定的接口或继承特定的类来定制和扩展框架的功能。

总的来说,SPISpring框架都倡导松耦合、模块化和可扩展性,通过定义接口和提供扩展点来实现这些目标。因此,可以说它们在思想上是相近的。

写在最后

拙作艰辛,字句心血,望诸君垂青,多予支持,不胜感激。


个人博客:无奈何杨(wnhyang)

个人语雀:wnhyang

共享语雀:在线知识共享

Github:wnhyang - Overview

相关推荐
小奏技术16 分钟前
国内APP的隐私进步,从一个“营销授权”弹窗说起
后端·产品
小研说技术34 分钟前
Spring AI存储向量数据
后端
苏三的开发日记34 分钟前
jenkins部署ruoyi后台记录(jenkins与ruoyi后台处于同一台服务器)
后端
苏三的开发日记36 分钟前
jenkins部署ruoyi后台记录(jenkins与ruoyi后台不在同一服务器)
后端
陈三一41 分钟前
MyBatis OGNL 表达式避坑指南
后端·mybatis
whitepure41 分钟前
万字详解JVM
java·jvm·后端
我崽不熬夜1 小时前
Java的条件语句与循环语句:如何高效编写你的程序逻辑?
java·后端·java ee
我崽不熬夜1 小时前
Java中的String、StringBuilder、StringBuffer:究竟该选哪个?
java·后端·java ee
我崽不熬夜2 小时前
Java中的基本数据类型和包装类:你了解它们的区别吗?
java·后端·java ee
每天学习一丢丢2 小时前
SpringBoot + Vue实现批量导入导出功能的标准方案
vue.js·spring boot·后端