Dubbo - SPI机制

Dubbo SPI

dubbo中采用了SPI思想,并没有使用jdk的SPI机制,而是自己实现的一套SPI机制。

jdk SPI 的缺点

  • JDK 标准的 SPI 会一次性加载实例化扩展点的所有实现,在 META-INF/service 下的文件里面加了 N 个实现类,那么 JDK 启动的时候都会一次性全部加载。那么如果有的扩展点实现初始化很耗时或者如果有些实现类并没有用到, 那么会很浪费资源
  • 如果扩展点加载失败,会导致调用方报错,而且这个错误很难定位到是这个原因

Dubbo SPI

Dubbo的源码中,很多地方会存在下面这样的三种代码,分别是自适应扩展点、指定名称的扩展点、激活扩展点。

//自适应扩展点,会使用默认的类,比如下面的Protocol默认使用的是DubboProtocol
ExtensionLoader.getExtensionLoader(xxx.class).getAdaptiveExtension();
//执行名称的扩展点,我们可以自定义实现类,并在配置文件(/META_INF/dubbo/interal/**)中指定其名称,然后调用此方法来获取指定的扩展类
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();  
  
} 

在META-INF/dubbo/interal/org.apache.dubbo.rpc.Protocol文件中存储了以下可能的扩展类

filter=org.apache.dubbo.rpc.cluster.filter.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
rest=org.apache.dubbo.rpc.protocol.rest.RestProtocol
grpc=org.apache.dubbo.rpc.protocol.grpc.GrpcProtocol
tri=org.apache.dubbo.rpc.protocol.tri.TripleProtocol
registry=org.apache.dubbo.registry.integration.InterfaceCompatibleRegistryProtocol
service-discovery-registry=org.apache.dubbo.registry.integration.RegistryProtocol
qos=org.apache.dubbo.qos.protocol.QosProtocolWrapper

执行完之后ExtensionLoader中缓存的ExtensionLoaders

Dubbo - SPI机制_第1张图片

里面存储了一个map
Dubbo - SPI机制_第2张图片

Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();运行过程中会读取上述文件中的扩展类,然后逐个加载每个类(未实例化),然后会通过javassist一个class文件。我们看一下自动生成的代码。

package com.chouxiaozi.dubboprovider;

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();
        //实际上会根据@SPI注解标注的名称再次通过ExtensionLoader.getExtensionLoader来实例化
        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);
    }

    public java.util.List getServers() {
        throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
    }
}

所以这就看到了dubbo的SPI机制默认是怎么玩的了,其实就是Protocol接口,@SPI(“dubbo”) 说的是,通过 SPI 机制来提供实现类,实现类是通过 dubbo 作为默认 key 去配置文件里找到的,配置文件名称与接口全限定名一样的,通过 dubbo 作为 key 可以找到默认的实现类就是 org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol

实际上这个地方并没有如此简单,你会发现在生成的类中extName并不都是dubbo,而有可能是其他,extName,这就会看传入的invoke是什么类型了
比如:在dubbo暴露服务接口的时候,extName的名字为service-discovery-registry,其真正执行的类为registryProtocol,他的export方法回去zookpeer上暴露接口

如果想要动态替换掉默认的实现类,需要使用 @Adaptive 接口,Protocol 接口中,有两个方法加了 @Adaptive 注解,就是说那俩接口会被代理实现。

比如这个 Protocol 接口搞了俩 @Adaptive 注解标注了方法,在运行的时候会针对 Protocol 生成代理类,这个代理类的那俩方法里面会有代理代码,代理代码会在运行的时候动态根据 url 中的 protocol 来获取那个 key,默认是 dubbo,你也可以自己指定,你如果指定了别的 key,那么就会获取别的实现类的实例了

实际上,还有另一种方式,

  • 上面Protocol接口是利用了@Adaptive修饰接口方法,然后spi会生成一个Protocol$Adaptive代理类去执行最终的DubboProtocol。
  • 而另一种方式是利用@Adaptive修饰类,这样就会直接使用@Adaptive修饰的类来创建对象。比如
@SPI
public interface ExtensionFactory {
    <T> T getExtension(Class<T> type, String name);
}

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {}

这里@SPI没有标注名称,那就会使用@Adaptive修饰的实现类来创建对象

源码分析
重要成员与构造方法
//存储所有被创建过得ExtensionLoader
private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64);
//存储所有实例
private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(64);
private final Holder<Object> cachedAdaptiveInstance = new Holder<>();
//存储所有非
private final Map<String, Object> cachedActivates = Collections.synchronizedMap(new LinkedHashMap<>());
private volatile Class<?> cachedAdaptiveClass = null;
private ExtensionLoader(Class<?> type) {
    this.type = type;//接口类型
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); 
}
getExtensionLoader方法
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
	//省略check。。。
    
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));//获取不到,则会new 一个ExtensionLoader
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}
getAdaptiveExtension方法 获取实例
public T getAdaptiveExtension() {
    //先从缓存中获取
    Object instance = cachedAdaptiveInstance.get();
    if (instance == 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);
                }
            }
        }
    }

    return (T) instance;
}
createAdaptiveExtension
private T createAdaptiveExtension() {
    try {
    	//调用getAdaptiveExtensionClass获取class对象   
        //调用newInstance创建实例
        //injectExtension来注入扩展
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
    }
}
getAdaptiveExtensionClass
private Class<?> getAdaptiveExtensionClass() {
    getExtensionClasses();//从配置文件中获取所有的类,并加载。同时根据类的而不同特点放入不同的容器(也就是对象的成员属性)中
    //如果加载完之后存在cachedAdaptiveClass(也就是@Adaptive标注的实现类),则直接返回。
    /*
    	这个地方就是@Adaptive修饰类后 ,直接使用此类的原因
    */
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    //如果不存在@Adaptive修饰的类,则去创建类
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
// #createAdaptiveExtensionClass
private Class<?> createAdaptiveExtensionClass() {
    //这里就是Protocl接口 最终生成类的地方了。
    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);
}

生成类的接口更加繁琐,我们只需要知道,其最终生成的类只是一个代理,最终会默认使用@SPI标注的名称作为key去 容器中寻找对应的类

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;
}
private Map<String, Class<?>> loadExtensionClasses() {
    cacheDefaultExtensionName();

    Map<String, Class<?>> extensionClasses = new HashMap<>();

    for (LoadingStrategy strategy : strategies) {
        loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());//加载目录下的文件
        loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
    }
    return extensionClasses;
}

loadDirectory后面的调用栈深度较多,我们直接看最后调用的方法;找到对应的文件后,读取每一行的 配置 (格式为 key=value,key是键值,value是对应的类)

loadClass
//clazz为候选类全限定名,通过class.forName加载到jvm中,然后调用loadClass方法
loadClass(extensionClasses, resourceURL, Class.forName(clazz, true, classLoader), name, overridden);

private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
                           boolean overridden) throws NoSuchMethodException {
    //重点:
    //如果类具有Adaptive注解,则赋值  给全局变量cachedAdaptiveClass
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        cacheAdaptiveClass(clazz, overridden);
    } else if (isWrapperClass(clazz)) {//是包装类的则放入cachedWrapperClasses中(通过判断是否含有本类型的参数构造函数来确定是否是包装类)
        cacheWrapperClass(clazz);
    } else {
        //其他的存到成员属性  cachedActivates中
        clazz.getConstructor();
        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)) {
            cacheActivateClass(clazz, names[0]);
            for (String n : names) {
                cacheName(clazz, n);
                saveInExtensionClass(extensionClasses, clazz, n, overridden);
            }
        }
    }
}
//到此已经全部加载完了

参考:

https://blog.csdn.net/yangbaggio/article/details/97617750

你可能感兴趣的:(dubbo,java,dubbo)