12

SPI 在 Dubbo中 的应用

 3 years ago
source link: https://my.oschina.net/vivotech/blog/4809292
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

通过本文的学习,可以了解 Dubbo SPI 的特性及实现原理,希望对大家的开发设计有一定的启发性。

SPI 全称为 Service Provider Interface,是一种模块间组件相互引用的机制。其方案通常是提供方将接口实现类的全名配置在classPath下的指定文件中,由调用方读取并加载。这样需要替换某个组件时,只需要引入新的JAR包并在其中包含新的实现类和配置文件即可,调用方的代码无需任何调整。优秀的SPI框架能够提供单接口多实现类时的优先级选择,由用户指定选择哪个实现。

得益于这些能力,SPI对模块间的可插拔机制和动态扩展提供了非常好的支撑。

本文将简单介绍JDK自带的SPI,分析SPI和双亲委派的关系,进而重点分析DUBBO的SPI机制;比较两者有何不同,DUBBO的SPI带来了哪些额外的能力。

二、JDK自带SPI

提供者在classPath或者jar包的META-INF/services/目录创建以服务接口命名的文件,调用者通过java.util.ServiceLoader加载文件内容中指定的实现类。

1. 代码示例

  • 首先定义一个接口Search

search示例接口

package com.example.studydemo.spi;
public interface Search {
    void search();
}
  • 实现类FileSearchImpl实现该接口

文件搜索实现类

package com.example.studydemo.spi;
public class FileSearchImpl implements Search {
    @Override
    public void search() {
        System.out.println("文件搜索");
    }
}
  • 实现类DataBaseSearchImpl实现该接口

数据库搜索实现类

package com.example.studydemo.spi;
public class DataBaseSearchImpl implements Search {
    @Override
    public void search() {
        System.out.println("数据库搜索");
    }
}
  • 在项目的META-INF/services文件夹下,创建Search文件

3c953d3bbf95dc7f3b5d547ff6e18b6e.webp

文件内容为:

com.example.studydemo.spi.DataBaseSearchImpl
com.example.studydemo.spi.FileSearchImpl
import java.util.ServiceLoader;
public class JavaSpiTest {
    public static void main(String[] args) {
        ServiceLoader<Search> searches = ServiceLoader.load(Search.class);
        searches.forEach(Search::search);
    }
}
e04e96fb07a32e26a6164152ab45a753.webp

2. 简单分析

ServiceLoader作为JDK提供的一个服务实现查找工具类,调用自身load方法加载Search接口的所有实现类,然后可以使用for循环遍历实现类进行方法调用。

有一个疑问:META-INF/services/目录是硬编码的吗,其它路径行不行?答案是不行。

跟进到ServiceLoader类中,第一行代码就是private static final String PREFIX = “META-INF/services/”,所以SPI配置文件只能放在classPath或者jar包的这个指定目录下面。

ServiceLoader的文件载入路径

public final class ServiceLoader<S>
    implements Iterable<S>
{
    //硬编码写死了文件路径
    private static final String PREFIX = "META-INF/services/";
 
    // The class or interface representing the service being loaded
    private final Class<S> service;
 
    // The class loader used to locate, load, and instantiate providers
    private final ClassLoader loader;

JDK SPI的使用比较简单,做到了基本的加载扩展组件的功能,但有以下几点不足:

  • 需要遍历所有的实现并实例化,想要找到某一个实现只能循环遍历,一个一个匹配;
  • 配置文件中只是简单的列出了所有的扩展实现,而没有给他们命名,导致在程序中很难去准确的引用它们;
  • 扩展之间彼此存在依赖,做不到自动注入和装配,不提供上下文内的IOC和AOP功能;
  • 扩展很难和其他的容器框架集成,比如扩展依赖了一个外部spring容器中的bean,原生的JDK SPI并不支持。

三、SPI与双亲委派

1. SPI加载到何处

基于类加载的双亲委派原则,由JDK内部加载的class默认应该归属于bootstrap类加载器,那么SPI机制加载的class是否也属于bootstrap呢 ?

答案是否定的,原生SPI机制通过ServiceLoader.load方法由外部指定类加载器,或者默认取Thread.currentThread().getContextClassLoader()线程上下文的类加载器,从而避免了class被载入bootstrap加载器。

2.SPI是否破坏了双亲委派

双亲委派的本质涵义是在rt.jar包和外部class之间建立一道classLoader的鸿沟,即rt.jar内的class不应由外部classLoader加载,外部class不应由bootstrap加载。

SPI仅是提供了一种在JDK代码内部干预外部class文件加载的机制,并未强制指定加载到何处;外部的class还是由外部的classLoader加载,未跨越这道鸿沟,也就谈不上破坏双亲委派。

原生ServiceLoader的类加载器

//指定类加载器
public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader)
//默认取前线程上下文的类加载器
public static <S> ServiceLoader<S> load(Class<S> service)

四、Dubbo SPI

Dubbo借鉴了Java SPI的思想,与JDK的ServiceLoader相对应的,Dubbo设计了ExtensionLoader类,其提供的功能比JDK更为强大。

1. 基本概念

首先介绍一些基本概念,让大家有一个初步的认知。

  • 扩展点(Extension Point):是一个Java的接口。
  • 扩展(Extension):扩展点的实现类
  • 扩展实例(Extension Instance):扩展点实现类的实例。
  • 自适应扩展实例(Extension Adaptive Instance)

自适应扩展实例其实就是一个扩展类的代理对象,它实现了扩展点接口。在调用扩展点的接口方法时,会根据实际的参数来决定要使用哪个扩展。

比如一个Search的扩展点,有一个search方法。有两个实现FileSearchImpl和DataBaseSearchImpl。Search的自适应实例在调用接口方法的时候,会根据search方法中的参数,来决定要调用哪个Search的实现。

如果方法参数中有name=FileSearchImpl,那么就调用FileSearchImpl的search方法。如果name=DataBaseSearchImpl,就调用DataBaseSearchImpl的search方法。 自适应扩展实例在Dubbo中的使用非常广泛。

在Dubbo中每一个扩展点都可以有自适应的实例,如果我们没有使用@Adaptive人工指定,Dubbo会使用字节码工具自动生成一个。

  • SPI Annotation

    作用于扩展点的接口上,表明该接口是一个扩展点,可以被Dubbo的ExtentionLoader加载

  • Adaptive

@Adaptive注解可以使用在类或方法上。用在方法上表示这是一个自适应方法,Dubbo生成自适应实例时会在方法中植入动态代理的代码。方法内部会根据方法的参数来决定使用哪个扩展。

@Adaptive注解用在类上代表该实现类是一个自适应类,属于人为指定的场景,Dubbo就不会为该SPI接口生成代理类,最典型的应用如AdaptiveCompiler、AdaptiveExtensionFactory等。

@Adaptive注解的值为字符串数组,数组中的字符串是key值,代码中要根据key值来获取对应的Value值,进而加载相应的extension实例。比如new String[]{“key1”,”key2”},表示会先在URL中寻找key1的值,

如果找到则使用此值加载extension,如果key1没有,则寻找key2的值,如果key2也没有,则使用SPI注解的默认值,如果SPI注解没有默认值,则将接口名按照首字母大写分成多个部分,

然后以’.’分隔,例如org.apache.dubbo.xxx.YyyInvokerWrapper接口名会变成yyy.invoker.wrapper,然后以此名称做为key到URL寻找,如果仍没有找到则抛出IllegalStateException异常。

  • ExtensionLoader
    类似于Java SPI的ServiceLoader,负责扩展的加载和生命周期维护。ExtensionLoader的作用包括:解析配置文件加载extension类、生成extension实例并实现IOC和AOP、创建自适应的extension等,下文会重点分析。

  • 扩展名
    和Java SPI不同,Dubbo中的扩展都有一个名称,用于在应用中引用它们。比如
    registry=com.alibaba.dubbo.registry.integration.RegistryProtocol
    dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol

  • 加载路径
    Java SPI从/META-INF/services目录加载扩展配置,Dubbo从以下路径去加载扩展配置文件:
    META-INF/dubbo/internal
    META-INF/dubbo
    META-INF/services
    其中META-INF/dubbo对开发者发放,META-INF/dubbo/internal 这个路径是用来加载Dubbo内部的拓展点的。

2. 代码示例

定义一个接口,标注上dubbo的SPI注解,赋予默认值,并提供两个extension实现类

package com.example.studydemo.spi;
@SPI("dataBase")
public interface Search {
    void search();
}
public class FileSearchImpl implements Search {
    @Override
    public void search() {
        System.out.println("文件搜索");
    }
}
public class DataBaseSearchImpl implements Search {
    @Override
    public void search() {
        System.out.println("数据库搜索");
    }
}

在META-INF/dubbo 路径下创建Search文件

c7316f4faf8990efb47dcafaa1175cd5.webp

文件内容如下:

dataBase=com.example.studydemo.spi.DataBaseSearchImpl
file=com.example.studydemo.spi.FileSearchImpl

编写测试类进行测试,内容如下:

public class DubboSpiTest {
    public static void main(String[] args) {
        ExtensionLoader<Search> extensionLoader = ExtensionLoader.getExtensionLoader(Search.class);
        Search fileSearch = extensionLoader.getExtension("file");
        fileSearch.search();
        Search dataBaseSearch = extensionLoader.getExtension("dataBase");
        dataBaseSearch.search();
        System.out.println(extensionLoader.getDefaultExtensionName());
        Search defaultSearch = extensionLoader.getDefaultExtension();
        defaultSearch.search();
    }
}
a3c8c430e906bbbc1713ef69e0303c1d.webp

从代码示例上来看,Dubbo SPI与Java SPI在这几方面是类似的:

  • 接口及相应的实现
  • 加载类及加载具体实现

3源码分析

下面深入到源码看看SPI在Dubbo中是怎样工作的,以Protocol接口为例进行分析。

//1、得到Protocol的扩展加载对象extensionLoader,由这个加载对象获得对应的自适应扩展类
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
//2、根据扩展名获取对应的扩展类
Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("dubbo");

在获取扩展实例前要先获取Protocol接口的ExtensionLoader组件,通过ExtensionLoader来获取相应的Protocol实例Dubbo实际是为每个SPI接口都创建了一个对应的ExtensionLoader。

ExtensionLoader组件

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 interface!");
    }
    if(!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type(" + type +
                ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
    }
    //EXTENSION_LOADERS为ConcurrentMap,存储Class对应的ExtensionLoader
    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是一个 ConcurrentMap,以接口Protocol为key,以ExtensionLoader对象为value;保存的是Protocol扩展的加载类,第一次加载的时候Protocol还没有自己的接口加载类,需要实例化一个。

再看new ExtensionLoader<T>(type) 这个操作,下面为ExtensionLoader的构造方法:

rivate ExtensionLoader(Class<?> type) {
    this.type = type;
    objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

每一个ExtensionLoader都包含2个值:type和objectFactory,此例中type就是Protocol,objectFactory就是ExtensionFactory。

对于ExtensionFactory接口来说,它的加载类中objectFactory值为null。

对于其他的接口来说,objectFactory都是通过ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()来获取;objectFactory的作用就是为dubbo的IOC提供依赖注入的对象,可以认为是进程内多个组件容器的一个上层引用,

随着这个方法的调用次数越来越多,EXTENSION_LOADERS 中存储的 loader 也会越来越多。

自适应扩展类与IOC

得到ExtensionLoader组件之后,再看如何获得自适应扩展实例。

public T getAdaptiveExtension() {
    //cachedAdaptiveInstance为缓存的自适应对象,第一次调用时还没有创建自适应类,所以instance为null
    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("fail to create adaptive instance: " + t.toString(), t);
                    }
                }
            }
        }
        else {
            throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
        }
    }
 
    return (T) instance;
}

首先从cachedAdaptiveInstance缓存中获取,第一次调用时还没有相应的自适应扩展,需要创建自适应实例,创建后再将该实例放到cachedAdaptiveInstance缓存中。

创建自适应实例参考createAdaptiveExtension方法,该方法包含两部分内容:创建自适应扩展类并利用反射实例化、利用IOC机制为该实例注入属性。

private T createAdaptiveExtension() {
    try {
        //得到自适应扩展类并利用反射实例化,然后注入属性值
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can not create adaptive extenstion " + type + ", cause: " + e.getMessage(), e);
    }
}

再来分析getAdaptiveExtensionClass方法,以Protocol接口为例,该方法会做以下事情:获取所有实现Protocol接口的扩展类、如果有自适应扩展类直接返回、如果没有则创建自适应扩展类。

//该动态代理生成的入口
private Class<?> getAdaptiveExtensionClass() {
    //1.获取所有实现Protocol接口的扩展类
    getExtensionClasses();
    //2.如果有自适应扩展类,则返回
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    //3.如果没有,则创建自适应扩展类
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

getExtensionClasses方法会加载所有实现Protocol接口的扩展类,首先从缓存中获取,缓存中没有则调用loadExtensionClasses方法进行加载并设置到缓存中,如下图所示:

private Map<String, Class<?>> getExtensionClasses() {
    //从缓存中获取
    Map<String, Class<?>> classes = cachedClasses.get();
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                //从SPI配置文件中解析
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

loadExtensionClasses方法如下:首先获取SPI注解中的value值,作为默认扩展名称,在Protocol接口中SPI注解的value为dubbo,因此DubboProtocol就是Protocol的默认实现扩展。其次加载三个配置路径下的所有的Protocol接口的扩展实现。

// 此方法已经getExtensionClasses方法同步过。
private Map<String, Class<?>> loadExtensionClasses() {
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if(defaultAnnotation != null) {
        String value = defaultAnnotation.value();
        if(value != null && (value = value.trim()).length() > 0) {
            String[] names = NAME_SEPARATOR.split(value);
            if(names.length > 1) {
                throw new IllegalStateException("more than 1 default extension name on extension " + type.getName()
                        + ": " + Arrays.toString(names));
            }
            if(names.length == 1) cachedDefaultName = names[0];
        }
    }
     
    Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
    //分别从三个路径加载
    loadFile(extensionClasses, DUBBO_INTERNAL_DIRECTORY);
    loadFile(extensionClasses, DUBBO_DIRECTORY);
    loadFile(extensionClasses, SERVICES_DIRECTORY);
    return extensionClasses;
}
 
 
private static final String SERVICES_DIRECTORY = "META-INF/services/";
private static final String DUBBO_DIRECTORY = "META-INF/dubbo/";
private static final String DUBBO_INTERNAL_DIRECTORY = DUBBO_DIRECTORY + "internal/";

在加载配置路径下的实现中,其中有一个需要关注的点,如果其中某个实现类上有Adaptive注解,说明用户指定了自适应扩展类,那么该实现类就会被赋给cachedAdaptiveClass,在getAdaptiveExtensionClass方法中会被直接返回。

如果该变量为空,则需要通过字节码工具来创建自适应扩展类。

private Class<?> createAdaptiveExtensionClass() {
    //生成类代码
    String code = createAdaptiveExtensionClassCode();
    //找到类加载器
    ClassLoader classLoader = findClassLoader();
    //获取编译器实现类,此处为AdaptiveCompiler,此类上有Adaptive注解
    com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    //将类代码编译为Class
    return compiler.compile(code, classLoader);
}

createAdaptiveExtensionClass方法生成的类代码如下:

package com.alibaba.dubbo.rpc;
 
import com.alibaba.dubbo.common.extension.ExtensionLoader;
 
public class Protocol$Adpative implements com.alibaba.dubbo.rpc.Protocol {
    public void destroy() {
        throw new UnsupportedOperationException("method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }
 
    public int getDefaultPort() {
        throw new UnsupportedOperationException("method public abstract int com.alibaba.dubbo.rpc.Protocol.getDefaultPort() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
    }
 
    public com.alibaba.dubbo.rpc.Invoker refer(java.lang.Class arg0, com.alibaba.dubbo.common.URL arg1) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        com.alibaba.dubbo.common.URL url = arg1;
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.refer(arg0, arg1);
    }
 
    public com.alibaba.dubbo.rpc.Exporter export(com.alibaba.dubbo.rpc.Invoker arg0) throws com.alibaba.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("com.alibaba.dubbo.rpc.Invoker argument getUrl() == null");
        com.alibaba.dubbo.common.URL url = arg0.getUrl();
        String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
        if (extName == null)
            throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.rpc.Protocol) name from url(" + url.toString() + ") use keys([protocol])");
        com.alibaba.dubbo.rpc.Protocol extension = (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName);
        return extension.export(arg0);
    }
}

由字节码工具生成的类Protocol$Adpative在方法末尾调用了ExtensionLoader.getExtensionLoader(xxx).getExtension(extName)来满足adaptive的自适应动态特性。

传入的extName就是从url中获取的动态参数,用户只需要在代表DUBBO全局上下文信息的URL中指定protocol参数的取值,adaptiveExtentionClass就可以去动态适配不同的扩展实例。

再看属性注入方法injectExtension,针对public的只有一个参数的set方法进行处理,利用反射进行方法调用来实现属性注入,此方法是Dubbo SPI实现IOC功能的关键。

private T injectExtension(T instance) {
    try {
        if (objectFactory != null) {
            for (Method method : instance.getClass().getMethods()) {
                if (method.getName().startsWith("set")
                        && method.getParameterTypes().length == 1
                        && Modifier.isPublic(method.getModifiers())) {
                    Class<?> pt = method.getParameterTypes()[0];
                    try {
                        String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
                        Object object = objectFactory.getExtension(pt, property);
                        if (object != null) {
                            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;

Dubbo IOC 是通过set方法注入依赖,Dubbo首先会通过反射获取到实例的所有方法,然后再遍历方法列表,检测方法名是否具有set方法特征。若有则通过ObjectFactory获取依赖对象。

最后通过反射调用set方法将依赖设置到目标对象中。objectFactory在创建加载类ExtensionLoader的时候已经创建了,因为@Adaptive是打在类AdaptiveExtensionFactory上,所以此处就是AdaptiveExtensionFactory。

AdaptiveExtensionFactory持有所有ExtensionFactory对象的集合,dubbo内部默认实现的对象工厂是SpiExtensionFactory和SpringExtensionFactory,他们经过TreeSet排好序,查找顺序是优先先从SpiExtensionFactory获取,如果返回空在从SpringExtensionFactory获取。

//有Adaptive注解说明该类是自适应类,不需要程序自己创建代理类
@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
    //factories拥有所有ExtensionFactory接口的实现对象
    private final List<ExtensionFactory> factories;
     
    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));
        }
        factories = Collections.unmodifiableList(list);
    }
    //查找时会遍历factories,顺序优先从SpiExtensionFactory中获取,再从SpringExtensionFactory中获取,原因为初始化时getSupportedExtensions方法中使用TreeSet已经排序,见下图
    public <T> T getExtension(Class<T> type, String name) {
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }
}
public Set<String> getSupportedExtensions() {
    Map<String, Class<?>> clazzes = getExtensionClasses();
    return Collections.unmodifiableSet(new TreeSet<String>(clazzes.keySet()));
}

虽然有过度设计的嫌疑,但我们不得不佩服dubbo SPI设计的精巧。

  • 提供@Adaptive注解,既可以加在方法上通过参数动态适配到不同的扩展实例;又可以加在类上直接指定自适应扩展类。
  • 利用AdaptiveExtensionFactory统一了进程中的不同容器,将ExtensionLoader本身视为一个独立的容器,依赖注入时将会分别从Spring容器和ExtensionLoader容器中查找。

扩展实例和AOP

getExtension方法比较简单,重点在于createExtension方法,根据扩展名创建扩展实例。

public T getExtension(String name) {
   if (name == null || name.length() == 0)
       throw new IllegalArgumentException("Extension name == null");
   if ("true".equals(name)) {
       return getDefaultExtension();
   }
   Holder<Object> holder = cachedInstances.get(name);
   if (holder == null) {
       cachedInstances.putIfAbsent(name, new Holder<Object>());
       holder = cachedInstances.get(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;
}

createExtension方法中的部分内容上文已经分析过了,getExtensionClasses方法获取接口的所有实现类,然后通过name获取对应的Class。紧接着通过clazz.newInstance()来实例化该实现类,调用injectExtension为实例注入属性。

private T createExtension(String name) {
    //getExtensionClasses方法之前已经分析过,获取所有的扩展类,然后根据扩展名获取对应的扩展类
    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, (T) clazz.newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        //属性注入
        injectExtension(instance);
        Set<Class<?>> wrapperClasses = cachedWrapperClasses;
        if (wrapperClasses != null && wrapperClasses.size() > 0) {
            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 + ")  could not be instantiated: " + t.getMessage(), t);
    }
}

在方法的最后有一段对于WrapperClass包装类的处理逻辑,如果接口存在包装类实现,那么就会返回包装类实例。实现AOP的关键就是WrapperClass机制,判断一个扩展类是否是WrapperClass的依据,是看其constructor函数中是否包含当前接口参数。

如果有就认为是一个wrapperClass,最终创建的实例是一个经过多个wrapperClass层层包装的结果;在每个wrapperClass中都可以编入面向切面的代码,从而就简单实现了AOP功能。

Activate活性扩展

对应ExtensionLoader的getActivateExtension方法,根据多个过滤条件从extension集合中智能筛选出您所需的那一部分。

getActivateExtension方法

public List<T> getActivateExtension(URL url, String[] names, String group);

首先这个方法只会返回带有Activate注解的扩展类,但并非带有注解的扩展类都会被返回。

names是明确指定所需要的那部分扩展类,非明确指定的扩展类需要满足group过滤条件和Activate注解本身指定的key过滤条件,非明确指定的会按照Activate注解中指定的排序规则进行排序;

getActivateExtension的返回结果是上述两种扩展类的总和。

Activate注解类

*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
    /**
     * Group过滤条件。
     */
    String[] group() default {};
 
    /**
     * Key过滤条件。包含{@link ExtensionLoader#getActivateExtension}的URL的参数Key中有,则返回扩展。
     */
    String[] value() default {};
 
    /**
     * 排序信息,可以不提供。
     */
    String[] before() default {};
 
    /**
     * 排序信息,可以不提供。
     */
    String[] after() default {};
 
    /**
     * 排序信息,可以不提供。
     */
    int order() default 0;
}

活性Extension最典型的应用是rpc invoke时获取filter链条,各种filter有明确的执行优先级,同时也可以人为增添某些filter,filter还可以根据服务提供者和消费者进行分组过滤。

Dubbo invoke获取filter链条

List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), Constants.SERVICE_FILTER_KEY, Constants.PROVIDER);

以TokenFilter为例,其注解为@Activate(group = Constants.PROVIDER, value = Constants.TOKEN_KEY),表示该过滤器只在服务提供方才会被加载,同时会验证注册地址url中是否带了token参数,如果有token表示服务端注册时指明了要做token验证,自然就需要加载该filter。

反之则不用加载;此filter加载后的执行逻辑则是从url中获取服务端注册时预设的token,再从rpc请求的attachments中获取消费方设置的remote token,比较两者是否一致,若不一致抛出RPCExeption异常阻止消费方的正常调用。

Dubbo 所有的接口几乎都预留了扩展点,根据用户参数来适配不同的实现。如果想增加新的接口实现,只需要按照SPI的规范增加配置文件,并指向新的实现即可。

用户配置的Dubbo属性都会体现在URL全局上下文参数中,URL贯穿了整个Dubbo架构,是Dubbo各个layer组件间相互调用的纽带。

总结一下 Dubbo SPI 相对于 Java SPI 的优势:

  • Dubbo的扩展机制设计默认值,每个扩展类都有自己的名称,方便查找。
  • Dubbo的扩展机制支持IOC,AOP等高级功能。
  • Dubbo的扩展机制能和第三方IOC容器兼容,默认支持Spring Bean,也可扩展支持其他容器。
  • Dubbo的扩展类通过@Adaptive注解实现了动态代理功能,更强大的是它可以通过一个proxy映射多个不同的扩展类。
  • Dubbo的扩展类通过@Activate注解实现了不同扩展类的分组、过滤、排序功能,能够更好的适配较复杂的业务场景。

作者: Xie Xiaopeng


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK