乐趣区

关于java:SPI-在-Dubbo中-的应用

通过本文的学习,能够理解 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 文件

文件内容为:

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);
    }
}

后果为:

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 文件

文件内容如下:

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();}
}

后果为:

从代码示例上来看,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

退出移动版