关于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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理