Dubbo SPI机制
关于Dubbo的一些概念#
服务暴露,为某个服务创建一个中转对象(能接触到网络/能调用到本地 service)来接受网络请求,外部系统把请求的目标/方法/参数发送到中转对象,中转对象就能执行方法并返回结果到网络;
服务引入,找寻到中转对象,创建一个代理对象,代理对象向中转对象传请求参数,等待返回值;
在 Dubbo 的核心领域模型中:
-
Invoker
-
Invoker 是实体域,它是 Dubbo 的核心模型,其它模型都向它靠扰,或转换成它,它代表一个可执行体,可向它发起 invoke 调用,它有可能是一个本地的实现,也可能是一个远程的实现,也可能一个集群实现;
-
Invoker 代表一个可执行对象,可以是本地执行类的Invoker,比如provider端的服务实现类,通过反射实现最终的调用;也可以是一个远程通信执行类的Invoker,consumer端通过接口与provider端进行远程通信,provider端利用本地Invoker执行相应的方法并返回结果;还可以是聚合Invoker,consumer调用端可以将多个Invoker聚合成一个Invoker执行操作;
-
-
Protocol
-
Protocol 是服务域,它是 Invoker 暴露和引用的主功能入口,它负责 Invoker 的生命周期管理;
-
通信协议,默认的Protocol是DubboProtocol,通过Protocol创建Invoker对象,默认也就是DubboInvoker;
-
-
ProxyFactory
-
对于Consumer端来说通过ProxyFactory创建调用接口的代理对象,对于Provider端来说主要是包装本地执行的Invoker类;ProxyFactory接口的实现类有JdkProxyFactory和JavassistProxyFactory,而默认是JavassistProxyFactory;JdkProxyFactory是利用JDK自带的Proxy来动态代理目标对象的远程通信Invoker类;JavassistProxyFactory是利用Javassit字节码技术来创建的远程通信Invoker类;
-
-
Invocation
-
Invocation 是会话域,它持有调用过程中的变量,比如方法名,参数等;
-
-
URL
-
在Dubbo中,服务,注册中心,消费者,配置信息,元数据(元数据信息包括服务接口,及接口的方法信息)都可用URL表示为资源;Dubbo中URL 作为配置信息的统一格式,URL在Dubbo中被当作公共契约,所有扩展点都通过传递 URL 携带配置信息;
在Dubbo中,服务,注册中心,消费者,配置信息,元数据(元数据信息包括服务接口,及接口的方法信息)都可用URL表示为资源;Dubbo中URL 作为配置信息的统一格式,URL在Dubbo中被当作公共契约,所有扩展点都通过传递 URL 携带配置信息;
关于Dubbo的架构设计[https://dubbo.apache.org/zh/docs/v2.7/dev/design/]
#
SPI全称Service Provider Interface,是JDK提供的一套被第三方实现或扩展API的一种服务提供发现机制,为某个接口找寻接口的实现,动态替换的发现机制可用于框架扩展和替换组件;
Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制,调用者根据实际使用需要,启用、扩展、或者替换框架的实现策略;
-
Java SPI
Java的SPI实现类java.util.ServiceLoader
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class SPIServiceTest { public static void main(String[] args) { //获取所有的实现 ServiceLoader<SPIService> serviceLoader = ServiceLoader.load(SPIService. class ); Iterator<SPIService> iterator = serviceLoader.iterator(); while (iterator.hasNext()) { //循环执行所有的实现 SPIService spiService = iterator.next(); spiService.doService(); } } } |
-
Dubbo SPI
官方SPI的讲解 [https://dubbo.apache.org/zh/docs/v2.7/dev/source/dubbo-spi/] (注:官方文档链接已改)
Dubbo版本为 2.6.2
Dubbo 的扩展机制和Java的SPI机制非常相似,增加了如下功能:
-
-
可以方便地获取某一个想要的扩展实现;
-
对于扩展实现增加了
IOC
和AOP
-
举例来说:
接口B,实现类B1,B2
实现类A1含有setB的方法,会自动注入一个接口B的实现类,此时注入的的是一个动态生成的接口B的实现类B$Adpative,该实现类能够根据参数的不同,自动引入B1或B2来完成相应的功能;
在Dubbo中, 接口上有 @SPI
标注的,都表明此接口支持扩展,用于定义默认实现类,比如Protocol接口定义有@SPI("dubbo")注解,
默认调用的是DubboProtocol
类;
在 Dubbo 中,很多拓展都是通过 SPI 机制进行加载的,比如 Protocol、Cluster、LoadBalance 等。有时,有些拓展并不想在框架启动阶段被加载,而是希望在拓展方法被调用时,根据运行时参数进行加载(如通过URL对象参数进行加载);当拓展未被加载,那么拓展方法就无法被调用(静态方法除外);拓展方法未被调用,拓展就无法被加载;对于这个矛盾的问题,Dubbo 通过自适应拓展机制(@Adaptive)很好的解决了;
@Adaptive
该注解一般使用在方法上,代表自动生成和编译一个动态的Adpative类,一般是没有人工的代理类的实现,需要依靠Dubbo自动生成代理类,而这个代理类所对应的实例在调用某个方法时,如果该方法被@Adaptive修饰,则会从URL中取值作为扩展点名去加载实现类并实例化,最后再使用这个实例调用相应的方法,用来指定从URL中的哪个Key中获取(它主要用于SPI,因为SPI的类是不固定的,未知的扩展类,所以设计了动态的$Adpative类);扩展未知类它设计了Porotocol$Adaptive的类,通过ExtensionLoader.getExtensionLoader(Protocol.class).getExtension(SPI类) 来提取对象;
如果该注解使用在类上,则代表一个适配类,不会生成代理类对象,用于固定已知类;
com.alibaba.dubbo.common.extension.ExtensionLoader加载dubbo扩展点;
Dubbo 目前提供了两种 ExtensionFactory,分别是 SpiExtensionFactory 和 SpringExtensionFactory。前者用于创建自适应的拓展,后者是用于从 Spring 的 IOC 容器中获取所需的拓展;
ExtensionFactory继承结构
Jdk的SPI与 Dubbo 的SPI区别如下#
区别一#
Jdk的SPI会一次性实例化扩展点的所有实现(也就是每次都要要遍历所有的实现),如果有的扩展实现初始化是耗时的,但不需要用到,这会浪费资源;
Dubbo 的 SPI对于实例化的扩展点增加了缓存,在缓存中使用指定的key就可以换取,如果缓存中没有该实例,就会创建一个实例并放入到缓存;当获取扩展点没有实例会创建实例并放入缓存,类似一种按需加载;
1 | private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<String, Holder<Object>>(); |
com.alibaba.dubbo.common.extension.ExtensionLoader#getExtension
getExtensionLoader 方法用于从缓存中获取与拓展类对应的 ExtensionLoader,若缓存未命中,则调用createExtension方法创建一个新的实例;
#
AOP#
创建拓展对象
com.alibaba.dubbo.common.extension.ExtensionLoader#createExtension
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 | private T createExtension(String name) { Class<?> clazz = getExtensionClasses().get(name); if (clazz == null ) { throw findException(name); } try { T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null ) { //实例不存在,则以Class为key,实例化一个对象为value,存入map EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } //向实例进行依赖注入 injectExtension(instance); Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (wrapperClasses != null && !wrapperClasses.isEmpty()) { for (Class<?> wrapperClass : wrapperClasses) { //将wrapperClass实例替换原有的instance,将原有的instance实例作为参数传入构造方法 //这里是dubbo SPI的AOP,使用装饰者模式将原实例增强 //当有多个wrapper对象,上一次的实例会传入到本次构造方法的参数中,一层一层的包起来,执行构造方法 instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; } catch (Throwable t) { throw new IllegalStateException( "Extension instance(name: " + name + ", class: " + type + ") could not be instantiated: " + t.getMessage(), t); } } |
关于扩展点加载的入口,从com.alibaba.dubbo.common.extension.ExtensionLoader#loadExtensionClasses这里看起,loadExtensionClasses方法在getExtensionClasses方法上同步;
上面的extensionClasses是一个map,key是别名,value是具体的实现类,会从不同的地方寻找接口的所有实现类,这就是扩展的实现, 加载路径从上到下分别对应META-INF/dubbo/
,META-INF/dubbo/internal/
,META-INF/services/
,META-INF/dubbo/internal
是Dubbo内部实现的各种扩展都放在这个目录,可以看出/META-INF/services/
的优先级最高;
createExtension
方法执行流程如下
-
通过
getExtensionClasses
获取所有的拓展类 -
通过反射创建拓展对象
-
向拓展对象中注入依赖
-
将拓展对象包裹在相应的
Wrapper
对象中(对应的是上面的instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
)
例子如下:
-
创建一个扩展Dubbo SPI的接口
1 2 3 4 | @SPI public interface SPIServiceDemo { void run(); } |
- 创建SPIServiceDemo的实现类
1 2 3 4 5 6 7 8 | public class SPIServiceDemoImpl implements SPIServiceDemo { private final Logger logger = LoggerFactory.getLogger(getClass()); @Override public void run() { logger.info( "自定义SPI run..." ); } } |
1 2 3 4 5 6 7 8 | public class SPIServiceDemoImpl2 implements SPIServiceDemo { private final Logger logger = LoggerFactory.getLogger(getClass()); @Override public void run(URL url) { logger.info( "url:" + url + "自定义SPI2 run..." ); } } |
1 2 | spiservicedemo=com.alibaba.dubbo.demo.spi.SPIServiceDemoImpl spiservicedemo2=com.alibaba.dubbo.demo.spi.SPIServiceDemoImpl2 |
- 创建一个测试类
1 2 3 4 5 6 7 | public class SPIServiceDemoTest { public static void main(String[] args) { ExtensionLoader<SPIServiceDemo> loader = ExtensionLoader.getExtensionLoader(SPIServiceDemo. class ); SPIServiceDemo serviceDemo = loader.getExtension( "spiservicedemo" ); serviceDemo.run(); } } |
打印如下:
AOP切面增强,类似如下
1 2 3 4 5 6 7 8 9 10 | //环绕 try { //前置 ... //后置 } catch (Exception e){ //异常 } finally { //最终 } |
增强类型 | 场景 |
---|---|
前置增强 | 权限控制、记录调用日志 |
后置增强 | 统计分析结果数据 |
异常增强 | 通过日志记录方法异常信息 |
最终增强 | 释放资源 |
环绕增强 |
如果要AOP增强,需要创建一个装饰类,之后会被isWrapperClass这个方法检查,判断是否有构造方法;
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 | public class SPIServiceDemoWrapper implements SPIServiceDemo { private final Logger logger = LoggerFactory.getLogger(getClass()); private SPIServiceDemo spiServiceDemo; public SPIServiceDemoWrapper(SPIServiceDemo spiServiceDemo) { this .spiServiceDemo = spiServiceDemo; } @Override public void run() { logger.info( "环绕增强 -- 1" ); try { logger.info( "前置增强.." ); spiServiceDemo.run(); logger.info( "后置增强.." ); } catch (Exception e) { e.printStackTrace(); logger.error( "异常增强.." ); } finally { logger.info( "最终增强.." ); logger.info( "环绕增强 -- 2" ); } } } |
并在com.alibaba.dubbo.demo.SPIServiceDemo文件添加如下:
1 | spiservicedemowrapper=com.alibaba.dubbo.demo.spi.SPIServiceDemoWrapper |
当需要自适应扩展实现,可使用@Adaptive,使用@Adaptive需要传递 URL 携带配置信息;官方文档讲解[https://dubbo.apache.org/zh/docs/v2.7/dev/source/adaptive-extension/]
入口方法为getAdaptiveExtension,该方法会调用createAdaptiveExtension生成代理类,实现自适应;
AdaptiveExtensionFactory实现ExtensionFactory接口,且用@Adaptive注解标注,在com.alibaba.dubbo.common.extension.ExtensionLoader#loadClass会检测注解,并赋值给cachedAdaptiveClass;
1 2 3 4 5 6 7 8 9 | if (clazz.isAnnotationPresent(Adaptive. class )) { if (cachedAdaptiveClass == null ) { cachedAdaptiveClass = clazz; } else if (!cachedAdaptiveClass.equals(clazz)) { throw new IllegalStateException( "More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName()); } } |
1 2 3 4 5 6 7 8 | private T createAdaptiveExtension() { try { //getAdaptiveExtensionClass获取代理类 return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException( "Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e); } } |
com.alibaba.dubbo.common.extension.factory.AdaptiveExtensionFactory#AdaptiveExtensionFactory
1 2 3 4 5 6 7 8 9 | public AdaptiveExtensionFactory() { ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory. class ); List<ExtensionFactory> list = new ArrayList<ExtensionFactory>(); for (String name : loader.getSupportedExtensions()) { list.add(loader.getExtension(name)); } //存储ExtensionFactory所有的实现类 factories = Collections.unmodifiableList(list); } |
createAdaptiveExtension会调用getAdaptiveExtensionClass 方法,当调用createAdaptiveExtensionClass方法会检测注解,并解析URL的配置信息;
1 2 3 4 5 6 7 8 9 10 | private Class<?> getAdaptiveExtensionClass() { // 通过 SPI 获取所有的拓展类 getExtensionClasses(); // 检查缓存,若缓存不为空,则直接返回缓存 if (cachedAdaptiveClass != null ) { return cachedAdaptiveClass; } // 创建自适应拓展类 return cachedAdaptiveClass = createAdaptiveExtensionClass(); } |
将上面测试接口修改如下:
1 2 3 4 5 | @SPI public interface SPIServiceDemo { @Adaptive ({ "k1" }) void run(URL url); } |
修改实现类
1 2 3 4 5 6 7 8 | public class SPIServiceDemoImpl implements SPIServiceDemo { private final Logger logger = LoggerFactory.getLogger(getClass()); @Override public void run(URL url) { logger.info( "url:" + url + "自定义SPI run..." ); } } |
1 2 3 4 5 6 7 8 | public class SPIServiceDemoImpl2 implements SPIServiceDemo { private final Logger logger = LoggerFactory.getLogger(getClass()); @Override public void run(URL url) { logger.info( "url:" + url + "自定义SPI2 run..." ); } } |
测试类
1 2 3 4 5 6 7 8 9 10 11 | public class SPIServiceDemoTest { public static void main(String[] args) { ExtensionLoader<SPIServiceDemo> loader = ExtensionLoader.getExtensionLoader(SPIServiceDemo. class ); SPIServiceDemo adaptiveExtension = loader.getAdaptiveExtension(); Map<String, String> map = new HashMap<String, String>(); map.put( "k1" , "spiservicedemo2" ); URL url = new URL( "test" , "127.0.0.1" , 1 , map); adaptiveExtension.run(url); } } |
打印如下:
IOC#
Dubbo SPI的 IOC 是通过 setter方法注入依赖,Dubbo 首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有 setter方法特征。若有,则通过 objectFactory 获取代理对象,最后通过反射调用 setter 方法将依赖设置到目标对象中;
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 | private T injectExtension(T instance) { try { if (objectFactory != null ) { // 遍历目标类的所有方法 for (Method method : instance.getClass().getMethods()) { // 检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 public if (method.getName().startsWith( "set" ) && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers())) { // 获取 setter 方法参数类型 Class<?> pt = method.getParameterTypes()[ 0 ]; try { // 获取属性名,比如 setName 方法对应属性名 name String property = method.getName().length() > 3 ? method.getName().substring( 3 , 4 ).toLowerCase() + method.getName().substring( 4 ) : "" ; // pt为注入的代理对象,从 ObjectFactory 中获取代理对象 Object object = objectFactory.getExtension(pt, property); if (object != null ) { // 通过反射调用 setter 方法设置依赖 method.invoke(instance, object); } } catch (Exception e) { logger.error( "fail to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(), e); } } } } } catch (Exception e) { logger.error(e.getMessage(), e); } return instance; } |
对于objectFactory 为SpiExtensionFactory类型,可注入的对象只能为接口类型且接口带有@SPI标注;
上面的objectFactory 变量的类型为 AdaptiveExtensionFactory,AdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory;
1 2 3 4 5 6 7 8 9 10 | public <T> T getExtension(Class<T> type, String name) { for (ExtensionFactory factory : factories) { //factory为ExtensionFactory的实现类,getExtension返回一个待注入的代理对象 T extension = factory.getExtension(type, name); if (extension != null ) { return extension; } } return null ; } |
org.apache.dubbo.common.extension.ExtensionLoader#createAdaptiveExtensionClass动态生成编译类,注:2.7+版本将生成代理类的封装到org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator#AdaptiveClassCodeGenerator,之前都是在该方法进行逻辑处理;
1 2 3 4 5 6 7 8 | private Class<?> createAdaptiveExtensionClass() { // 生成代理类的代码 String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate(); ClassLoader classLoader = findClassLoader(); // 动态生成编译类 org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler. class ).getAdaptiveExtension(); return compiler.compile(code, classLoader); } |
Compiler是SPI接口类,通过ExtensionLoader进行加载;
org.apache.dubbo.common.compiler.Compiler的内容如下:
1 2 3 | adaptive=com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler jdk=com.alibaba.dubbo.common.compiler.support.JdkCompiler javassist=com.alibaba.dubbo.common.compiler.support.JavassistCompiler |
Java 字节码以二进制的形式存储在 .class 文件中,每一个 .class 文件包含一个 Java 类或接口,之后加载到JVM上生成一个实例;Javassist (JAVA programming ASSISTant) 是在 Java 中编辑字节码的类库,它使Java 程序能够在运行时定义一个新类,并在JVM 加载时修改类文件;使用这种方式可以在程序在运行过程中动态生成需要的类的实例;[http://www.javassist.org/tutorial/tutorial.html]
而在Dubbo中,JavassistCompiler类是Dubbo对Javaassist类库的封装增强;
Compiler接口默认的实现类为JavassistCompiler
1 2 3 4 5 6 7 8 9 10 11 12 13 | @SPI ( "javassist" ) public interface Compiler { /** * Compile java source code. * * @param code Java source code * @param classLoader classloader * @return Compiled class */ Class<?> compile(String code, ClassLoader classLoader); } |
这三个Compiler使用JavassistCompiler作为Compiler接口的实现类,但在AdaptiveCompiler类定义上面有一个@Adaptive注解,表示是一个装饰模式的类,AdaptiveCompiler起包装作用,在里面获取当前的实现类JavassistCompiler,然后执行compiler方法产生默认的自适应扩展类(代理类);
ExtensionLoader大致流程如下:
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· ASP.NET Core 模型验证消息的本地化新姿势
· 对象命名为何需要避免'-er'和'-or'后缀
· SQL Server如何跟踪自动统计信息更新?
· AI与.NET技术实操系列:使用Catalyst进行自然语言处理
· 分享一个我遇到过的“量子力学”级别的BUG。
· dotnet 源代码生成器分析器入门
· Draw.io:你可能不知道的「白嫖级」图表绘制神器
· ASP.NET Core 模型验证消息的本地化新姿势
· 从零开始:基于 PyTorch 的图像分类模型
· [WPF] 在RichTextBox中输出Microsoft.Extension.Logging库的