国庆期间闲来无事,写了一个简单的小程序,小程序名称叫做 IT藏经楼。目的是分享这些年自己积累的一些学习材料,方面大家查找使用,包括电子书、案例项目、学习视频、面试题和一些PPT模板。里面所有材料都免费分享。目前小程序中只发布了非常小的一部分,后续会陆续上传分享。当前版本的小程序页面也比较简单,还在逐渐的优化中。

在Dubbo中,SPI贯穿整个Dubbo的核心,所以理解Dubbo中的SPI对于理解Dubbo的原理有着至关重要的作用。在Spring中,我们知道SpringFactoriesLoader这个类,它也是一种SPI机制。
关于Java SPI
在了解Dubbo的SPI机制之前,我们先了解下Java提供的SPI (service provider interface) 机制,SPI是JDK内置的一种服务提供发现机制。目前市面上很多框架都用它来做服务的扩展发现。简单的说,它是一种动态替换发现的机制。
举个简单的例子,我们想在运行时动态给它添加实现,你只需要添加一个实现,然后把新的实现描述给JDK知道就行了。大家耳熟能详的如JDBC,日志框架都有用到。
实现 SPI 需要遵循的标准
我们如何去实现一个标准的 SPI 发现机制呢?其实很简单,只需要满足以下提交就行了 :
- 需要在 classpath 下创建一个目录,该目录命名必须是:META-INF/service
- 在该目录下创建一个 properties 文件,该文件需要满足以下几个条件 :
2.1 文件名必须是扩展的接口的全路径名称
2.2 文件内部描述的是该扩展接口的所有实现类
2.3 文件的编码格式是 UTF-8 - 通过 java.util.ServiceLoader 的加载机制来发现

SPI 的实际应用
SPI 在很多地方有应用,可能大家都没有关注,最常用的就是 JDBC 驱动,我们来看看是怎么应用的。
SPI 的缺点
- JDK 标准的 SPI 会一次性加载实例化扩展点的所有实现,什么意思呢?就是如果你在 META-INF/service 下的文件里面加了 N 个实现类,那么 JDK 启动的时候都会一次性全部加载。那么如果有的扩展点实现初始化很耗时或者如果有些实现类并没有用到, 那么会很浪费资源
- 如果扩展点加载失败,会导致调用方报错,而且这个错误很难定位到是这个原因
Dubbo中的SPI机制
Dubbo也用了SPI思想,不过没有用JDK的SPI机制,是自己实现的一套SPI机制。在Dubbo的源码中,很多地方会存在下面这样的三种代码,分别是自适应扩展点、指定名称的扩展点、激活扩展点。
ExtensionLoader.getExtensionLoader(xxx.class).getAdaptiveExtension(); ExtensionLoader.getExtensionLoader(xxx.class).getExtension(name); ExtensionLoader.getExtensionLoader(xxx.class).getActivateExtension(url, key);
比如:
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
Protocol接口,在运行的时候dubbo会判断一下应该选用这个Protocol接口的哪个实现类来实例化对象。
它会去找你配置的Protocol,将你配置的Protocol实现类加载到JVM中来,然后实例化对象,就用你配置的那个Protocol实现类就可以了。
上面那行代码就是dubbo里面大量使用的,就是对很多组件,都是保留一个接口和多个实现,然后在系统运行的时候动态的根据配置去找到对应的实现类。如果你没有配置,那就走默认的实现类。
@SPI("dubbo") public interface Protocol {
int getDefaultPort(); @Adaptive <T> Exporter<T> export(Invoker<T> invoker) throws RpcException; @Adaptive <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException; void destroy(); }
在dubbo自己的jar中,在META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol文件中:
filter=org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper listener=org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper mock=org.apache.dubbo.rpc.support.MockProtocol dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol injvm=org.apache.dubbo.rpc.protocol.injvm.InjvmProtocol rmi=org.apache.dubbo.rpc.protocol.rmi.RmiProtocol hessian=org.apache.dubbo.rpc.protocol.hessian.HessianProtocol http=org.apache.dubbo.rpc.protocol.http.HttpProtocol org.apache.dubbo.rpc.protocol.webservice.WebServiceProtocol thrift=org.apache.dubbo.rpc.protocol.thrift.ThriftProtocol native-thrift=org.apache.dubbo.rpc.protocol.nativethrift.ThriftProtocol memcached=org.apache.dubbo.rpc.protocol.memcached.MemcachedProtocol redis=org.apache.dubbo.rpc.protocol.redis.RedisProtocol rest=org.apache.dubbo.rpc.protocol.rest.RestProtocol xmlrpc=org.apache.dubbo.xml.rpc.protocol.xmlrpc.XmlRpcProtocol registry=org.apache.dubbo.registry.integration.RegistryProtocol qos=org.apache.dubbo.qos.protocol.QosProtocolWrapper
所以这就看到了dubbo的SPI机制默认是怎么玩的了,其实就是Protocol接口,@SPI(“dubbo”) 说的是,通过 SPI 机制来提供实现类,实现类是通过 dubbo 作为默认 key 去配置文件里找到的,配置文件名称与接口全限定名一样的,通过 dubbo 作为 key 可以找到默认的实现类就是 org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
如果想要动态替换掉默认的实现类,需要使用 @Adaptive 接口,Protocol 接口中,有两个方法加了 @Adaptive 注解,就是说那俩接口会被代理实现。
比如这个 Protocol 接口搞了俩 @Adaptive 注解标注了方法,在运行的时候会针对 Protocol 生成代理类,这个代理类的那俩方法里面会有代理代码,代理代码会在运行的时候动态根据 url 中的 protocol 来获取那个 key,默认是 dubbo,你也可以自己指定,你如果指定了别的 key,那么就会获取别的实现类的实例了。
如何自己扩展 dubbo 中的组件
在调用处执行如下代码 :
public class App {
public static void main( String[] args ) {
// Main.main(args); Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("myProtocol"); System.out.println(protocol.getDefaultPort()); } }
我们的猜想是,一定有一个地方通过读取指定路径下的所有文件进行 load。然后讲对应的结果保存到一个 map 中,key 对应为 名称,value 对应为实现类。那么这个实现,一定就在 ExtensionLoader 中了。接下来我们就可以基于这个猜想去看看代码的实 现。
Dubbo 的扩展点原理实现
在看Dubbo SPI的实现代码之前,我们先思考一个问题,所谓的扩展点,就是通过指定目录下配置一个对应接口的实现类,然后程序会进行查找和解析,找到对应的扩展点,那么这里就涉及到两个问题:
- 怎么解析
- 被加载的类如何存储和使用
ExtensionLoader.getExtensionLoader.getExtension
我们通过上面的例子可以知道,我们是通过下面这个代码去加载扩展点的:
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("myProtocol");
我们从这段代码着手,去看看到底做了什么事情,能够通过这样一段代码实现扩展协议的查找和加载。
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
if (type == null) {
throw new IllegalArgumentException("Extension type == null"); } if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!"); } if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); } ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; }
从上面代码可以看出, 先会去检查我们想要的扩展点是否已经存在于EXTENSION_LOADERS这个缓存中,如果存在则直接返回,否则新创建一个ExtensionLoader。
private ExtensionLoader(Class<?> type) {
this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }
如果当前的 type=ExtensionFactory,那么 objectFactory=null, 否则会创建一个自适应扩展点给到 objectFacotry,目前来说具 体做什么咱们先不关心,现在只要知道objectFactory 在这里赋值了,并且是返回一个 AdaptiveExtension(). 这个暂时不展开,后面再分析
getExtension
public T getExtension(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("Extension name == null"); } if ("true".equals(name)) {
return getDefaultExtension(); } Holder<Object> holder = getOrCreateHolder(name); Object instance = holder.get(); if (instance == null) {
synchronized (holder) {
instance = holder.get(); if (instance == null) {
instance = createExtension(name); holder.set(instance); } } } return (T) instance; }
这个方法就是根据一个名字来获得一个对应类的实例,所以我们来猜想一下,回想一下前面咱们配置的自定义协议,name 实际上就是 myProtocol,而返回的实现类应该就是 MyProtocol。
同样的,先去看缓存中是否已经存在我们想要的扩展点,如果没有则新建一个
createExtension
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) {
EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } injectExtension(instance); Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (CollectionUtils.isNotEmpty(wrapperClasses)) {
for (Class<?> wrapperClass : wrapperClasses) {
instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; } catch (Throwable t) {
throw new IllegalStateException("Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: " + t.getMessage(), t); } }
这里会先调用getExtensionClasses()加载指定目录下的所有文件:
private Map<String, Class<?>> getExtensionClasses() {
Map<String, Class<?>> classes = cachedClasses.get(); if (classes == null) {
synchronized (cachedClasses) {
classes = cachedClasses.get(); if (classes == null) {
classes = loadExtensionClasses(); cachedClasses.set(classes); } } } return classes; } // synchronized in getExtensionClasses private Map<String, Class<?>> loadExtensionClasses() {
cacheDefaultExtensionName(); Map<String, Class<?>> extensionClasses = new HashMap<>(); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName()); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); return extensionClasses; }
injectExtension
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
//objectFactory在这里用到了 for (Method method : instance.getClass().getMethods()) {
if (isSetter(method)) {
/ * Check {@link DisableInject} to see if we need auto injection for this property */ if (method.getAnnotation(DisableInject.class) != null) {
continue; } Class<?> pt = method.getParameterTypes()[0]; if (ReflectUtils.isPrimitives(pt)) {
continue; } try {
String property = getSetterProperty(method); Object object = objectFactory.getExtension(pt, property); if (object != null) {
method.invoke(instance, object); } } catch (Exception e) {
logger.error("Failed to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(), e); } } } } } catch (Exception e) {
logger.error(e.getMessage(), e); } return instance; }
这个方法是用来实现依赖注入的,如果被加载的实例中,有成员属性本身也是一个扩展点,则会通过 set 方法进行注入。
分析到这里我们发现,所谓的扩展点,套路都一样,不管是 springfactorieyLoader,还是 Dubbo 的 spi。实际上,Dubbo 的功能 会更加强大,比如自适应扩展点,比如依赖注入
Adaptive 自适应扩展点
什么叫自适应扩展点呢?我们先演示一个例子,在下面这个例子中,我们传入一个 Compiler 接口,它会返回一个 AdaptiveCompiler。这个就叫自适应。
public class App {
public static void main( String[] args ) {
Compiler compiler=ExtensionLoader.getExtensionLoader(Compiler.class).getAdaptiveExtension(); System.out.println(compiler.getClass()); } }
比如拿 Protocol 这个接口来说,它里面定义了 export 和 refer 两个抽象方法,这两个方法分别带有@Adaptive 的标识,标识是 一个自适应方法。 我们知道 Protocol 是一个通信协议的接口,具体有多种实现,那么这个时候选择哪一种呢? 取决于我们在使用 dubbo 的时候所 配置的协议名称。而这里的方法层面的 Adaptive 就决定了当前这个方法会采用何种协议来发布服务。
@SPI("dubbo") public interface Protocol {
// 省略部分代码 @Adaptive <T> Exporter<T> export(Invoker<T> invoker) throws RpcException; @Adaptive <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException; // 省略部分代码 }
getAdaptiveExtension
这个方法主要就是要根据传入的接口返回一个自适应的实现类 :
public T getAdaptiveExtension() {
Object instance = cachedAdaptiveInstance.get(); if (instance == null) {
if (createAdaptiveInstanceError == null) {
synchronized (cachedAdaptiveInstance) {
instance = cachedAdaptiveInstance.get(); if (instance == null) {
try {
instance = createAdaptiveExtension(); cachedAdaptiveInstance.set(instance); } catch (Throwable t) {
createAdaptiveInstanceError = t; throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t); } } } } else {
throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); } } return (T) instance; }
cachedAdaptiveInstance是一个缓存,如果缓存中没有,则通过createAdaptiveExtension创建一个。
private T createAdaptiveExtension() {
try {
return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) {
throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e); } } private Class<?> getAdaptiveExtensionClass() {
getExtensionClasses(); if (cachedAdaptiveClass != null) {
return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
createAdaptiveExtension这个方法主要做了以下两件事:
- 获得一个自适应扩展点的实例
- 实现依赖注入
getAdaptiveExtensionClass方法中先调用getExtensionClasses()方法,这个方法我们前面已经提到,会加载当前传入的类型的所有扩展点,保存在一个 hashmap 中 这里有一个判断逻辑,如果 cachedApdaptiveClas!=null ,直接返回这个 cachedAdaptiveClass,这里大家可以猜一下,这个 cachedAdaptiveClass 是一个什么?
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); }
dubbo 会动态生成一个代理类 Protocol$Adaptive. 前面的名字 protocol 是根据当前 ExtensionLoader 所加载的扩展点来定义的。
Protocol$Adaptive
动态生成的代理类,以下是我通过 debug 拿到的代理类
前面传入进来的 cachedDefaultName,在这个动态生成的类中,会体现在下面标红的部分,也就是它的默认实现是 DubboProtocol
import org.apache.dubbo.common.extension.ExtensionLoader; public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
public void destroy() {
throw new UnsupportedOperationException( "The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!"); } public int getDefaultPort() {
throw new UnsupportedOperationException( "The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!"); } public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null"); if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null"); org.apache.dubbo.common.URL url = arg0.getUrl(); String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); if (extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])"); org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader .getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName); return extension.export(arg0); } public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
if (arg1 == null) throw new IllegalArgumentException("url == null"); org.apache.dubbo.common.URL url = arg1; String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); if (extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])"); org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader .getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName); return extension.refer(arg0, arg1); } }
我们可以看到在export方法中,会先通过String extName = (url.getProtocol() == null ? “dubbo” : url.getProtocol());去获取extName, 在我们之前的例子中,url.getProtocol()方法返回的应该是”myProtocol”,所以extName是myProtocol,然后通过下面代码去获取Protocol的实例:
org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader .getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
图形理解
简单来说,上面的基于方法层面的@Adaptive,基本实现原理的图形大概是这样

injectExtension
对于扩展点进行依赖注入,简单来说就是如果当前加载的扩展点中存在一个成员属性(对象),并且提供了 set 方法,那么这个 方法就会执行依赖注入.
private T injectExtension(T instance) {
try {
if (objectFactory != null) {
for (Method method : instance.getClass().getMethods()) {
if (isSetter(method)) {
/ * Check {@link DisableInject} to see if we need auto injection for this property */ if (method.getAnnotation(DisableInject.class) != null) {
continue; } Class<?> pt = method.getParameterTypes()[0]; if (ReflectUtils.isPrimitives(pt)) {
continue; } try {
String property = getSetterProperty(method); Object object = objectFactory.getExtension(pt, property); if (object != null) {
method.invoke(instance, object); } } catch (Exception e) {
logger.error("Failed to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(), e); } } } } } catch (Exception e) {
logger.error(e.getMessage(), e); } return instance; }
在 injectExtension 这个方法中,我们发现入口出的代码首先判断了 objectFactory 这个对象是否为空。这个是在哪里初始化的呢? 实际上我们在获得 ExtensionLoader 的时候,就对 objectFactory 进行了初始化。
private ExtensionLoader(Class<?> type) {
this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }
Activate 自动激活扩展点
自动激活扩展点,有点类似我们讲 springboot 的时候用到的 conditional,根据条件进行自动激活。但是这里设计的初衷是,对 于一个类会加载多个扩展点的实现,这个时候可以通过自动激活扩展点进行动态加载, 从而简化配置我们的配置工作。
@Activate 提供了一些配置来允许我们配置加载条件,比如 group 过滤,比如 key 过滤。
public class App {
public static void main( String[] args ) {
ExtensionLoader<Filter> loader=ExtensionLoader.getExtensionLoader(Filter.class); URL url= new URL("","",0); // url=url.addParameter("cache","cache"); List<Filter> filters=loader.getActivateExtension(url,"cache"); System.out.println(filters.size()); filters.forEach(filter -> {
System.out.println(filter); }); } }


发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/224801.html原文链接:https://javaforall.net
