当前位置: 首页 > news >正文

Dubbo SPI机制

SPI是什么?

  1. SPI出现的背景:在 Java 开发中,经常会依赖第三方库(jar 包)。这些库里可能定义了一些 标准接口(比如数据库驱动、日志接口、序列化接口),并在内部调用了接口的方法。但是我们想在自己的业务中扩展这个接口(写新的实现类),又不能直接修改第三方库的源码,需要一种解耦的机制,让第三方库可以“发现”并调用我们的实现类。
  2. SPI解决方案:通过一种约定的目录(如 META-INF/services/接口全限定名),在文件中声明接口与实现类的映射关系,第三方框架ServiceLoader通过获取文件内容,动态加载实现类,在运行时接入我们的扩展类。

JDK SPI

JDK SPI缺点

  1. jdk 自带SPI机制,实例化是用的反射无参构造方法,不支持构造器注入
  2. jdk 自带SPI机制,不支持依赖注入
  3. jdk 自带SPI机制,会将全部实现类加载,不支持指定实现类加载

Demo:

public interface TestService {String test();
}
public class TestServiceImpl implements TestService {@Overridepublic String test() {return "我是TestServiceImpl";}
}
ServiceLoader<TestService> testServices = ServiceLoader.load(TestService.class);
Iterator<TestService> iterator = testServices.iterator();
while(iterator.hasNext()) {System.out.println(iterator.next().test());
}

配置文件如下:
在这里插入图片描述
输出如下:
在这里插入图片描述

Dubbo SPI

Dubbo 扩展能力

  1. 支持依赖注入(IOC
  2. 支持获取指定实现类
  3. 支持wrap自动装配(理解为装饰器模式、AOP
  4. 支持自动激活(获取批量实现类)
  5. 支持自定义自适应类、动态生成自适应类(根据参数获取具体实现类,通常方法参数需要包含URL或者参数包含getUrl方法)

常用api

  1. ExtensionLoader.getExtension(“key”):获取xxx接口文件key为key的实现类
  2. ExtensionLoader.getAdaptiveExtension():获取xxx接口的自适应扩展类
  3. ExtensionLoader.getActivateExtensions():获取xxx接口的自动装配类(标注了@Activate的实现类)

Demo:

@SPI("openAi")
public interface AiService {@Adaptivevoid requestAi();
}
public class OpenAiServiceImpl implements AiService {@Overridepublic void requestAi() {System.out.println("我是OpenAiServiceImpl");}
}
 ExtensionLoader<AiService> extensionLoader = ScopeModelUtil.getModuleModel(null).getExtensionLoader(AiService.class);AiService openAi = extensionLoader.getExtension("openAi");openAi.requestAi();

配置文件:
在这里插入图片描述
输出:
在这里插入图片描述

Dubbo SPI 原理与图解

流程图

ExtensionLoader.getExtension(“key”) 流程图

在这里插入图片描述

ExtensionLoader.getAdaptiveExtension() 流程图

在这里插入图片描述

ExtensionLoader.getActivateExtensions()流程图

在这里插入图片描述

源码解析

扫描文件:
private Map<String, Class<?>> loadExtensionClasses() throws InterruptedException {...// 缓存默认自适应类,@SPI注解的value字段cacheDefaultExtensionName();Map<String, Class<?>> extensionClasses = new HashMap<>();// META-INF/dubbo/internal/;META-INF/dubbo/;META-INF/services/;扫描三个路径 用jdk spi机制实现			.for (LoadingStrategy strategy : strategies) {loadDirectory(extensionClasses, strategy, type.getName());...}return extensionClasses;
}private void loadDirectoryInternal(Map<String, Class<?>> extensionClasses, LoadingStrategy loadingStrategy, String type)throws InterruptedException {// 指定目录 + 类全命名String fileName = loadingStrategy.directory() + type;try {List<ClassLoader> classLoadersToLoad = new LinkedList<>();...// 加载文件Map<ClassLoader, Set<java.net.URL>> resources =ClassLoaderResourceLoader.loadResources(fileName, classLoadersToLoad);resources.forEach(((classLoader, urls) -> {loadFromClass(extensionClasses,loadingStrategy.overridden(),urls,classLoader,loadingStrategy.includedPackages(),loadingStrategy.excludedPackages(),loadingStrategy.onlyExtensionClassLoaderPackages());}));}...}
文件每行全命名类归纳(@adaptive、wrap、@Activate):
    private void loadResource(Map<String, Class<?>> extensionClasses,ClassLoader classLoader,java.net.URL resourceURL,boolean overridden,String[] includedPackages,String[] excludedPackages,String[] onlyExtensionClassLoaderPackages) {try {List<String> newContentList = getResourceContent(resourceURL);String clazz;for (String line : newContentList) {try {// 解析格式为key=valueString name = null;int i = line.indexOf('=');if (i > 0) {name = line.substring(0, i).trim();clazz = line.substring(i + 1).trim();} else {clazz = line;}if (StringUtils.isNotEmpty(clazz) && !isExcluded(clazz, excludedPackages)&& isIncluded(clazz, includedPackages)&& !isExcludedByClassLoader(clazz, classLoader, onlyExtensionClassLoaderPackages)) {// 加载具体类信息loadClass(classLoader, extensionClasses, resourceURL, Class.forName(clazz, true, classLoader), name, overridden);}} ...}}}private void loadClass(ClassLoader classLoader,Map<String, Class<?>> extensionClasses,java.net.URL resourceURL,Class<?> clazz,String name,boolean overridden) {if (!type.isAssignableFrom(clazz)) {throw new IllegalStateException("Error occurred when loading extension class (interface: " + type + ", class line: "+ clazz.getName() + "), class " + clazz.getName() + " is not subtype of interface.");}boolean isActive = loadClassIfActive(classLoader, clazz);if (!isActive) {return;}// 带有@Adaptive的类 缓存spi自适应扩展类if (clazz.isAnnotationPresent(Adaptive.class)) {cacheAdaptiveClass(clazz, overridden);} else if (isWrapperClass(clazz)) {// 缓存wrap自动装配类 后续aop铺垫cacheWrapperClass(clazz);} else {if (StringUtils.isEmpty(name)) {name = findAnnotationName(clazz);if (name.length() == 0) {throw new IllegalStateException("No such extension name for the class " + clazz.getName()+ " in the config " + resourceURL);}}String[] names = NAME_SEPARATOR.split(name);if (ArrayUtils.isNotEmpty(names)) {// 带有@activate注解 的类 缓存自动装配类cacheActivateClass(clazz, names[0]);for (String n : names) {cacheName(clazz, n);saveInExtensionClass(extensionClasses, clazz, n, overridden);}}}}
依赖注入 与 wrap自动装配:
private T createExtension(String name, boolean wrap) {// 获取key=name的类Class<?> clazz = getExtensionClasses().get(name);if (clazz == null || unacceptableExceptions.contains(name)) {throw findException(name);}try {T instance = (T) extensionInstances.get(clazz);if (instance == null) {// 创建实例extensionInstances.putIfAbsent(clazz, createExtensionInstance(clazz));instance = (T) extensionInstances.get(clazz);instance = postProcessBeforeInitialization(instance, name);// set方法 依赖注入injectExtension(instance);instance = postProcessAfterInitialization(instance, name);}if (wrap) {List<Class<?>> wrapperClassesList = new ArrayList<>();if (cachedWrapperClasses != null) {wrapperClassesList.addAll(cachedWrapperClasses);// 排序 @Wrapper order 进行排序wrapperClassesList.sort(WrapperComparator.COMPARATOR);Collections.reverse(wrapperClassesList);}if (CollectionUtils.isNotEmpty(wrapperClassesList)) {for (Class<?> wrapperClass : wrapperClassesList) {Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);// 根据wrapper注解 条件属性 进行过滤boolean match = (wrapper == null)|| ((ArrayUtils.isEmpty(wrapper.matches())|| ArrayUtils.contains(wrapper.matches(), name))&& !ArrayUtils.contains(wrapper.mismatches(), name));if (match) {// 装饰器模式 层层嵌套 Aop instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));instance = postProcessAfterInitialization(instance, name);}}}}initExtension(instance);return instance;} catch (Throwable t) {throw new IllegalStateException("Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: "+ t.getMessage(),t);}}

如果对你有帮助,辛苦点个赞,谢谢啦,朋友!!!


文章转载自:

http://CHhVaeZJ.ryywf.cn
http://9ulbdfwH.ryywf.cn
http://9VbeBHU4.ryywf.cn
http://c3vL5DVh.ryywf.cn
http://OCFYltY1.ryywf.cn
http://fjYNzKn6.ryywf.cn
http://IkPmxPGs.ryywf.cn
http://8owoJzVE.ryywf.cn
http://A5KJDzGP.ryywf.cn
http://GGn4PCyS.ryywf.cn
http://kXNN1n8I.ryywf.cn
http://VajdJYI7.ryywf.cn
http://hAUBLjGd.ryywf.cn
http://fMTZzpiM.ryywf.cn
http://II6GFnyv.ryywf.cn
http://rFXaH9N1.ryywf.cn
http://ANksYFs2.ryywf.cn
http://IVRnsEAI.ryywf.cn
http://WTn0yYWl.ryywf.cn
http://96xGzHT5.ryywf.cn
http://O1XBQSBu.ryywf.cn
http://gE84EMXO.ryywf.cn
http://dfTCGgSr.ryywf.cn
http://NWxXwHbC.ryywf.cn
http://fovgIMUA.ryywf.cn
http://y3JNOxyV.ryywf.cn
http://3JnNTWlA.ryywf.cn
http://vGtue6ts.ryywf.cn
http://OrI6lKlc.ryywf.cn
http://VTMYASv6.ryywf.cn
http://www.dtcms.com/a/381362.html

相关文章:

  • 《Linux 基础指令实战:新手入门的命令行操作核心教程(第一篇)》
  • 【开题答辩全过程】以 “饭否”食材搭配指南小程序的设计与实现为例,包含答辩的问题和答案
  • RabbitMQ 在实际开发中的应用场景与实现方案
  • 有没有什么办法能批量去除很多个PDF文件的水印
  • JavaScript 内存管理与常见泄漏排查(闭包、DOM 引用、定时器、全局变量)
  • ArkAnalyzer源码初步分析I——分析ts项目流程
  • Linux_基础指令(二)
  • 什么是子网?
  • 【前端】【utils】高效文件下载技术解析
  • FastAPI 中内省函数 inspect.signature() 作用
  • 【Linux】Linux进程概念(上)
  • 前端vue使用canvas封装图片标注功能,鼠标画矩形框,标注文字 包含下载标注之后的图片
  • 水库运行综合管理平台
  • langgraph astream使用详解
  • 日语学习-日语知识点小记-构建基础-JLPT-N3阶段(31):文法運用第9回3+(考え方11)
  • shell脚本练习:文件检查与拷贝
  • 书籍成长书籍文字#创业付费杂志《财新周刊》2025最新合集 更33期
  • 《AI游戏开发中的隐性困境:从战斗策略失效到音效错位的深度破局》
  • UVM寄存器模型与通道机制
  • 一个简单的GPU压力测试脚本-python版
  • Linux x86 stability和coredump
  • Claude-Flow AI协同开发:从“CTO”到“人机共生体”的AI协同开发
  • CPR_code
  • 【连接器专题】FPC连接器基础及连接器选型指南
  • 精准、可控、高一致性:谷歌Nano Banana正在终结AI“抽卡”时代
  • 操作系统实时性的影响因素总结
  • 国际避税方法有哪些
  • 开发避坑指南(47):IDEA 2025.1.3 运行main函数报错:CreateProcess error=206, 文件名或扩展名太长的解决方案
  • 《苍穹外卖》项目日记_Day9
  • 文件检查与拷贝-简化版