SpringCloud Alibaba系列-6Dubbo的SPI机制分析

CSDN地址:https://blog.csdn.net/Eclipse_2019/article/details/125992076

学习目标

  1. 理解Dubbo的SPI机制
  2. 能口述Dubbo和JDK中的SPI机制的区别

第1章 SPI简介

SPI ,全称为 Service Provider Interface,是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。 这一机制为很多框架扩展提供了可能,比如在Dubbo、JDBC中都使用到了SPI机制。我们先通过一个很简单的例子来看下它是怎么用的。

简单来说,SPI是一种扩展机制,核心就是将服务配置化,在核心代码不用改动的前提下,通过加载配置文件中的服务,然后根据传递的参数来决定到底走什么逻辑,走哪个服务的逻辑。这样就对扩展是开放的,对修改是关闭的。

SPI的运用场景

当你在写核心代码的时候,如果某个点有涉及到会根据参数的不同走不同的逻辑的时候,如果没有SPI,你可能会在代码里面写大量的if else代码,这样代码就非常不灵活,假设有一天又新增了一种逻辑,代码里面也要跟着改,这个就违背了开闭原则,SPI的出现就是解决这种扩展问题的,你可以把实现类全部都配置到配置文件中,然后在核心代码里面就只要加载配置文件,然后根据入参跟加载的这些类进行匹配,如果匹配的就走该逻辑,这样如果有一天新增了逻辑,核心代码是不用变的,唯一变的就是自己工程里面的配置文件和新增类,符合了开闭原则。

第2章 JDK中的SPI机制

前面已经介绍过SPI是什么以及在哪些地方用了,在这里就重点介绍一下SPI在JDK中的实现,下面我们看看具体的

2.1 案例

1.api

public interface GLog {
    boolean support(String type);
    void debug();
    void info();
}

2.消费端

public class Log4j implements GLog {
    @Override
    public boolean support(String type) {
        return "log4j".equalsIgnoreCase(type);
    }
 
    @Override
    public void debug() {
        System.out.println("====log4j.debug======");
    }
 
    @Override
    public void info() {
        System.out.println("====log4j.info======");
    }
}
public class Logback implements GLog {
    @Override
    public boolean support(String type) {
        return "Logback".equalsIgnoreCase(type);
    }
 
    @Override
    public void debug() {
        System.out.println("====Logback.debug======");
    }
 
    @Override
    public void info() {
        System.out.println("====Logback.info======");
    }
}
public class Slf4j implements GLog {
    @Override
    public boolean support(String type) {
        return "Slf4j".equalsIgnoreCase(type);
    }
 
    @Override
    public void debug() {
        System.out.println("====Slf4j.debug======");
    }
 
    @Override
    public void info() {
        System.out.println("====Slf4j.info======");
    }
}

3.消费端文件配置

在resources/META-INF/services目录创建文件,文件名称必须跟接口的完整限定名相同。如图:

SpringCloud Alibaba系列——6Dubbo的SPI机制分析

这个接口文件中配置了该接口的所有实现类的完整限定名,如图:

SpringCloud Alibaba系列——6Dubbo的SPI机制分析

现在要根据输入的参数来决定到底是走Log4j的逻辑还是Logback的逻辑。

4.测试

public class MyTest {
    //不同的入参,对应调用的逻辑是不一样的
    public static void main(String[] args) {
        //这个是我们业务的核心代码,核心代码会根据外部的参数决定要掉哪一个实例
        //可以去读配置文件 properties配置,去决定掉哪个实例
        //jdk api 加载配置文件配置实例
        ServiceLoader all = ServiceLoader.load(GLog.class);
        Iterator iterator = all.iterator();
        Scanner scanner = new Scanner(System.in);
        String s = scanner.nextLine();
        while (iterator.hasNext()) {
            GLog next = iterator.next();
            //这个实例是不是我们需要掉的
            // 策略模式 当前实例是不是跟入参匹配
            if(next.support(s)) {
                next.debug();
            }
        }
    }
}

2.2 总结

大家可以把这个单元测试代码看成是你自己业务中的核心代码,这个代码是不需要改动的,扩展新增的只是接口对应的实现类而已,然后在核心代码中我们只要根据传入的参数去调用不同的逻辑就可以了,这个就是SPI扩展的魅力,核心代码不需要改动。

从上面的测试代码我希望大家还了解一个点,JDK中的SPI也是获取类实例的一种方式,然后配合策略模式就可以根据参数选择实例调用了。

第3章 dubbo中的SPI机制

dubbo中的spi机制大体上的流程跟jdk中的spi类似的,但是也有很多细节方面不一样,下面我们就重点看看dubbo中的spi机制。

3.1 案例

1.代码都写在消费端

//这个是必须的
@SPI("spring")
public interface ActivateApi {
    @Adaptive
    String todo(String param, URL url);
}

2.接口实现

//@Adaptive
public class DubboActivate implements ActivateApi {
    @Override
    public String todo(String param, URL url) {
        return param;
    }
}
public class MybatisActivate implements ActivateApi {
    private ActivateApi activateApi;
    @Override
    public String todo(String param, URL url) {
        return param;
    }
    public void setActivateApi(ActivateApi activateApi) {
        this.activateApi = activateApi;
        System.out.println(activateApi);
    }
}
public class Rabbitmq1Activate implements ActivateApi {
    @Override
    public String todo(String param, URL url) {
        return param;
    }
}
public class Rabbitmq2Activate implements ActivateApi {
    @Override
    public String todo(String param, URL url) {
        return param;
    }
}
public class RabbitmqActivate implements ActivateApi {
    @Override
    public String todo(String param, URL url) {
        return param;
    }
}
public class SpringActivate implements ActivateApi {
    @Override
    public String todo(String param, URL url) {
        System.out.println("spring");
        return param;
    }
}
public class SpringCloudActivate implements ActivateApi {
    @Override
    public String todo(String param, URL url) {
        System.out.println("springcloud");
        return param;
    }
}

3.文件配置

在resources/META-INF/dubbo下面配置接口文件,文件名称必须跟接口完整限定名相同,如图:

SpringCloud Alibaba系列——6Dubbo的SPI机制分析

接口文件中配置的内容就是该接口的实现类的完整限定名,跟jdk中配置不一样的地方就是,dubbo中可以带key,这个key就是该实现类的映射key,可以根据这个key获取到该key对应的类实例。

com.example.dubbospi.DubboActivate
mybatis=com.example.dubbospi.MybatisActivate
rabbitmq1=com.example.dubbospi.Rabbitmq1Activate
rabbitmq2=com.example.dubbospi.Rabbitmq2Activate
rabbitmq=com.example.dubbospi.RabbitmqActivate
spring=com.example.dubbospi.SpringActivate
springcloud=com.example.dubbospi.SpringCloudActivate

4.单元测试

public class TestDubboSpi {
    @Test
    public void adaptive() {
        ActivateApi adaptiveExtension = ExtensionLoader.getExtensionLoader(ActivateApi.class).getAdaptiveExtension();
        System.out.println(adaptiveExtension.getClass());
        URL url = new URL("test","localhost",29015);
        url = url.addParameter("activate.api","mybatis");
//        url.addParameter("activate.api","mybatis");
        adaptiveExtension.todo("eclipse2019",url);
    }
 
    @Test
    public void getDefualt() {
        ActivateApi defaultExtension = ExtensionLoader.getExtensionLoader(ActivateApi.class).getDefaultExtension();
        System.out.println(defaultExtension);
    }
 
    /**
     getActivateExtension
     1、首先看分组,如果值@Activate只配置了分组,那么就只匹配分组值
     2、会匹配url中的参数,如果分组和value都配置了。首先匹配分组,然后在匹配url中的参数key
     */
    @Test
    public void test1() {
        URL url = URL.valueOf("test://localhost/test");
        url = url.addParameter("rabbitmq","gggg");
        //只看分组,不看url中的参数
        List rabbitmq = ExtensionLoader.getExtensionLoader(ActivateApi.class).getActivateExtension(url,new String[]{"rabbitmq"} ,"rabbitmq");
        System.out.println(rabbitmq.size());
        for (ActivateApi activateApi : rabbitmq) {
            System.out.println(activateApi.getClass());
        }
    }
 
    @Test
    public void getExtension() {
//        ActivateApi ac = ExtensionLoader.getExtensionLoader(ActivateApi.class).getExtension("mybatis");
//        System.out.println(ac);
 
        Protocol po = ExtensionLoader.getExtensionLoader(Protocol.class).getExtension("dubbo");
    }
 
    @Test
    public void xx() {
        System.out.println(ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension());
    }
 
    @Test
    public void providerReg() {
        String url = "dubbo%3A%2F%2F192.168.67.3%3A20990%2Fcom.example.service.UserService%3Fanyhost%3Dtrue%26application%3Ddubbo_provider%26deprecated%3Dfalse%26dubbo%3D2.0.2%26dynamic%3Dtrue%26generic%3Dfalse%26interface%3Dcom.example.service.UserService%26metadata-type%3Dremote%26methods%3DdoKill%2CqueryUser%26pid%3D14092%26release%3D3.0.2.1%26retries%3D7%26revision%3D1.0-SNAPSHOT%26service-name-mapping%3Dtrue%26side%3Dprovider%26threadpool%3Dfixed%26threads%3D100%26timeout%3D5000%26timestamp%3D1635058443480";
        RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
        Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://127.0.0.1:2181"));
        registry.register(URL.valueOf(URL.decode(url)));
    }
}

下面我们来介绍一下dubbo中几个非常重要的SPI的API。

3.2 getAdaptiveExtension

3.2.1 方法功能

该方法是获取到一个接口的实现类,获取的方式有:

获取类上有@Adaptive注解的类的实例

如果接口的所有实现类都没有@Adaptive注解则dubbo动态生成一个

3.2.2 方法用法

//需要获取什么接口的实现类,getExtensionLoader中就传该接口的类型

ActivateApi adaptiveExtension =

ExtensionLoader.getExtensionLoader(ActivateApi.class).getAdaptiveExtension();

3.2.3 源码分析

1、ExtensionLoader

ExtensionLoader.getExtensionLoader(ActivateApi.class),该方法调用是获取一个ExtensionLoader对象,核心就是会根据不同的接口类型创建不同的ExtensionLoader对象,换句话说就是一个接口类型对应这个ExtensionLoader对象。

public static  ExtensionLoader getExtensionLoader(Class type) {
    if (type == null) {
        throw new IllegalArgumentException("Extension type == null");
    }
    if (!type.isInterface()) {
        throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
    }
    //如果接口上没有@SPI注解,则报错
    if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
    }
    //从缓存中获取
    ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        //每一个@SPI接口类型都会对应一个ExtensionLoader对象
        //这里需要知道,一个接口类型对应着一个ExtensionLoader对象
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
        loader = (ExtensionLoader) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

2、getAdaptiveExtension

核心思想就是先从缓存中拿实例,如果没有才调用createAdaptiveExtension();方法创建实例。创建完成后放入到缓存中。缓存就是在ExtensionLoader中的一个Holder对象,如:Holder cachedAdaptiveInstance = new Holder<>();

public T getAdaptiveExtension() {
    //先从缓存中拿实例
    Object instance = cachedAdaptiveInstance.get();
    //DCL 思想
    if (instance == null) {
        if (createAdaptiveInstanceError != null) {
            throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(),createAdaptiveInstanceError);
        }
        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;
}

3、createAdaptiveExtension

该方法就是创建实例的方法,创建实例并对实例进行IOC属性的依赖注入。

private T createAdaptiveExtension() {
    try {
        //injectExtension 是dubbo中的ioc逻辑,对属性进行赋值操作
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can't create adaptive extension " + type + ",cause: " + e.getMessage(), e);
    }
}

4、getAdaptiveExtensionClass

获取到要实例化的类的Class对象并返回

private Class<?> getAdaptiveExtensionClass() {
    //核心方法 ,重点看 。建立名称和类的映射关系
    getExtensionClasses();
    //如果有@Adaptive注解的类,则返回该类
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    //动态拼凑类,动态编译生成
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

5、getExtensionClasses

该方法是一个非常核心的方法,dubbo spi中的很多API都需要先调用这个方法来建立key和class的映射关系。获取映射关系的流程也是先从缓存里面拿,如果缓存没有才读配置文件建立映射关系,并把映射关系缓存起来。

该方法的作用:

  • 建立key和class的映射关系
  • 给ExtensionLoader里面的很多全局变量赋值,这些全局变量在dubbo spi的api中有用到
private Map> getExtensionClasses() {
    //先从缓存拿
    Map> classes = cachedClasses.get();
    //DCL
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                //从本地文件中加载key和类的关系
                classes = loadExtensionClasses();
                //把加载到的映射关系缓存起来
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

6、loadExtensionClasses

该方法就是读取resources/META-INF/dubbo、META-INF/dubbo/internal/下面的配置文件,然后解析配置文件,建立key和class的映射关系,给ExtensionLoader里面的全局变量赋值等功能。

private Map> loadExtensionClasses() {
    //获取默认的实现类名称并缓存起来
    cacheDefaultExtensionName();
    Map> extensionClasses = new HashMap<>();
    //从jdk的spi机制中获取LoadingStrategy 实例
    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;
}

cacheDefaultExtensionName

设置ExtensionLoader中的全局变量cachedDefaultName的值,全局变量值的来源就是接口中@SPI("spring")注解的value值。

private void cacheDefaultExtensionName() {
    //获取类上的@SPI注解
    final SPI defaultAnnotation = type.getAnnotation(SPI.class);
    if (defaultAnnotation == null) {
        return;
    }
    String value = defaultAnnotation.value();
    //如果@SPI注解中有value值
    if ((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));
        }
        //把value值设置到 cachedDefaultName,,这个就是默认的实现类
        if (names.length == 1) {
            cachedDefaultName = names[0];
        }
    }
}

loadDirectory

加载目录下的所有文件,建立名称和类的映射关系 ,设置全局变量的值。循环调用loadResource

while (urls.hasMoreElements()) {
    java.net.URL resourceURL = urls.nextElement();
    //加载接口对应的文件的核心方法
    loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages);
}

loadResource

该方法就是对每一个文件进行处理的,建立名称和类的映射关系 ,设置全局变量的值。对读到的文件中的每一行数据进行处理。

private void loadResource(Map> extensionClasses, ClassLoader classLoader,java.net.URL resourceURL, boolean overridden, String... excludedPackages) {
    try {
        try (BufferedReader reader = new BufferedReader(new
                                                        InputStreamReader(resourceURL.openStream(), StandardCharsets.UTF_8))) {
            String line;
            String clazz = null;
            //读一行数据
            while ((line = reader.readLine()) != null) {
                final int ci = line.indexOf('#');
                //如果一行数据中有#号,则只要#前面那部分,,后面那部分可以写注释
                if (ci >= 0) {
                    line = line.substring(0, ci);
                }
                line = line.trim();
                if (line.length() > 0) {
                    try {
                        String name = null;
                        //=分割
                        int i = line.indexOf('=');
                        if (i > 0) {
                            //=号前面那部分是key
                            name = line.substring(0, i).trim();
                            //=号后面那部分则是类
                            clazz = line.substring(i + 1).trim();
                        } else {
                            clazz = line;
                        }
                        if (StringUtils.isNotEmpty(clazz) && !isExcluded(clazz,excludedPackages)) {
                            //加载类的核心逻辑
                            loadClass(extensionClasses, resourceURL, Class.forName(clazz,
                                                                                   true, classLoader), name, overridden);
                        }
                    } catch (Throwable t) {
                        IllegalStateException e = new IllegalStateException("Failed to load extension class (interface: " + type + ", class line: " + line + ") in " + resourceURL + ",cause: " + t.getMessage(), t);
                        exceptions.put(line, e);
                    }
                }
            }
        }
    } catch (Throwable t) {
        logger.error("Exception occurred when loading extension class (interface: " + type + ", class file: " + resourceURL + ") in " + resourceURL, t);
    }
}

loadClass

该方法的核心就是走了三套逻辑:

  • 1如果类上有@Adaptive注解
  • 如果类是包装类
  • 没有@Adaptive注解又不是包装类
private void loadClass(Map> extensionClasses, java.net.URL resourceURL,Class<?> clazz, String name,
                       boolean overridden) throws NoSuchMethodException {
    //如果类类型和接口类型不一致,报错
    if (!type.isAssignableFrom(clazz)) {
        throw new IllegalStateException("Error occurred when loading extension class (interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + " is not subtype of interface.");
    }
    //如果类上面有@Adaptive注解
    if (clazz.isAnnotationPresent(Adaptive.class)) {
        //在这个方法里面 对 cachedAdaptiveClass变量赋值
        cacheAdaptiveClass(clazz, overridden);
    } else if (isWrapperClass(clazz)) {
        //如果是包装类,包装类必然是持有目标接口的引用的,有目标接口对应的构造函数
        //对 cachedWrapperClasses 变量赋值
        cacheWrapperClass(clazz);
    } else {
        //获取类的无参构造函数,如果是包装类,这里会报错,,其实这里包装类走不进来了,包装类先处理的
        clazz.getConstructor();
        //如果没有配置key
        if (StringUtils.isEmpty(name)) {
            //如果类有Extension注解,则是注解的value,如果没注解则是类名称的小写做为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)) {
            //如果类上面有@Activate注解,则建立名称和注解的映射
            //对 cachedActivates 全局变量赋值
            cacheActivateClass(clazz, names[0]);
            for (String n : names) {
                cacheName(clazz, n);
                //这里 extensionClasses 建立了 key和class的映射
                saveInExtensionClass(extensionClasses, clazz, n, overridden);
            }
        }
    }
}

7、createAdaptiveExtensionClass

前面分析类建立映射关系的过程,如果cachedAdaptiveClass属性不为空,也就是有@Adaptive注解的类,则直接返回该类,如果该属性为空,则会走到createAdaptiveExtensionClass方法,该方法的核心作用:

  • 自动根据接口中的注解和参数配置生成类的字符串
  • 根据类的字符串动态编译生成字节码文件加载到jvm

接口定义的规则

  • 接口中的方法必须要有一个方法有@Adaptive注解,要不然会报错
  • 方法的参数中必须要有URL参数,要不然会报错

为什么要这样设计:因为这种方式dubbo会自动生成一个类,这个类其实就是一个代理类,但是dubbo并不知道应该都哪个逻辑,也就是这个代理类并不知道走该接口的哪个实现类,所以我们必须用代理对象掉方法的时候用方法的入参告诉dubbo应该走哪个实现类,而选择掉哪个实现类的参数就在URL参数中。所以参数中必须要有URL参数(ActivateApi接口中被注解@Adaptive修饰的方法,参数中必须带有URL参数),如下是dubbo生成的代理类:

import org.apache.dubbo.common.extension.ExtensionLoader;
public class ActivateApi$Adaptive implements com.example.dubbospi.ActivateApi {
    public java.lang.String todo(java.lang.String arg0, org.apache.dubbo.common.URL arg1) {
        if (arg1 == null) throw new IllegalArgumentException("url == null");
        org.apache.dubbo.common.URL url = arg1;
        String extName = url.getParameter("activate.api", "spring");
        if(extName == null) throw new IllegalStateException("Failed to get extension (com.example.dubbospi.ActivateApi) name from url (" + url.toString() + ") use keys([activate.api])");
        com.example.dubbospi.ActivateApi extension =
            (com.example.dubbospi.ActivateApi)ExtensionLoader.getExtensionLoader(com.example.dubbospi.Activate Api.class).getExtension(extName);
        return extension.todo(arg0, arg1);
    }
}

从代码中可以看出,是根据url入参来选择接口的某个实例,然后用该实例去掉方法,也就是ActivateApi$Adaptive这种类只是一个代理层,并没有实质性的业务逻辑,是一个根据参数选择实例掉用的过程。

8、injectExtension

前面我们分析到已经生成了一个实例了,该方法就是对该实例进行属性的依赖注入操作,其实是掉该实例的setxxx方法进行参数赋值

private T injectExtension(T instance) {
    if (objectFactory == null) {
        return instance;
    }
    try {
        //对实例中的setXXX方法进行属性注入
        for (Method method : instance.getClass().getMethods()) {
            if (!isSetter(method)) {
                continue;
            }
            /**
* Check {@link DisableInject} to see if we need auto injection for this property
*/
            //如果有DisableInject注解,则不注入
            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;
}

但是这里要强调的是,对调setxxx方法,参数值的来源

1、通过dubbo的spi方式获取到参数值

2、通过spring容器的getbean获取到参数值

dubbo spi的方式获取值

其实这种方法就是调用了getAdaptiveExtension方法获取到了实例

public class SpiExtensionFactory implements ExtensionFactory {
    @Override
    public  T getExtension(Class type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }
}

spring容器的方式获取值

这种方式就是掉getBean获取到容器中的实例

public class SpringExtensionFactory implements ExtensionFactory, Lifecycle {
    private static final Logger logger =
        LoggerFactory.getLogger(SpringExtensionFactory.class);
    private static final Set CONTEXTS = new
        ConcurrentHashSet();
    public static void addApplicationContext(ApplicationContext context) {
        CONTEXTS.add(context);
        if (context instanceof ConfigurableApplicationContext) {
            ((ConfigurableApplicationContext) context).registerShutdownHook();
            // see https://github.com/apache/dubbo/issues/7093
            DubboShutdownHook.getDubboShutdownHook().unregister();
        }
    }
    public static void removeApplicationContext(ApplicationContext context) {
        CONTEXTS.remove(context);
    }
    public static Set getContexts() {
        return CONTEXTS;
    }
    // currently for test purpose
    public static void clearContexts() {
        CONTEXTS.clear();
    }
    @Override
    @SuppressWarnings("unchecked")
    public  T getExtension(Class type, String name) {
        //SPI should be get from SpiExtensionFactory
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            return null;
        }
        for (ApplicationContext context : CONTEXTS) {
            T bean = BeanFactoryUtils.getOptionalBean(context, name, type);
            if (bean != null) {
                return bean;
            }
        }
        //logger.warn("No spring extension (bean) named:" + name + ", try to find an
        extension (bean) of type " + type.getName());
            return null;
    }
    @Override
    public void initialize() throws IllegalStateException {
        clearContexts();
    }
    @Override
    public void start() throws IllegalStateException {
        // no op
    }
    @Override
    public void destroy() {
        clearContexts();
    }
}

3.2.4 总结

getAdaptiveExtension方法是dubbo spi中一个分成核心的方法,做了两件事情,一个是获取实例,一个是在ExtensionLoader中赋值了全局变量,这个变量会用到其他api中。实例获取也分为两种,获取类上有@Adaptive注解的类的实例,一个是dubbo自动生成的代理实例,代理实例会根据接口配置来生成,接口的方法必须要有一个有@Adaptive注解,方法的入参必须要与URL参数。

3.3 getExtension("dubbo")

3.3.1 方法功能

获取key对应的类实例,分为两种不同的情况

  • 如果没有包装类则获取该key对应的类实例
  • 如果有包装类则获取包装类的实例并把key对应的类实例设置到包装类中

3.3.2 方法用法

@Test
public void getExtension() {
    ActivateApi ac = ExtensionLoader.getExtensionLoader(ActivateApi.class).getExtension("mybatis");
    System.out.println(ac);
}

3.3.3 源码分析

1、getExtension

根据key名称获取到key对应的类实例对象

public T getExtension(String name) {
    T extension = getExtension(name, true);
    if (extension == null) {
        throw new IllegalArgumentException("Not find extension: " + name);
    }
    return extension;
}
public T getExtension(String name, boolean wrap) {
    if (StringUtils.isEmpty(name)) {
        throw new IllegalArgumentException("Extension name == null");
    }
    //如果名称是true就返回默认的实例
    if ("true".equals(name)) {
        return getDefaultExtension();
    }
    final Holder holder = getOrCreateHolder(name);
    //如果缓存中实例为空
    Object instance = holder.get();
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                //创建实例
                instance = createExtension(name, wrap);
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

2、createExtension

根据key名称获取实例对象的核心代码,cachedWrapperClasses是之前getExtensionClasses()方法设置到ExtensionLoader中的全局变量值,里面添加了所有该接口的包装类。如下

private T createExtension(String name, boolean wrap) {
    //根据配置的名称找到相应的类 ,从映射关系中找
    Class<?> clazz = getExtensionClasses().get(name);
    if (clazz == null || unacceptableExceptions.contains(name)) {
        throw findException(name);
    }
    try {
        T instance = (T) EXTENSION_INSTANCES.get(clazz);
        if (instance == null) {
            //实例化并存入缓存
            EXTENSION_INSTANCES.putIfAbsent(clazz,clazz.getDeclaredConstructor().newInstance());
            instance = (T) EXTENSION_INSTANCES.get(clazz);
        }
        //对类进行ioc,,类中可能有些setXXX方法需要赋值
        injectExtension(instance);
        //如下是对包装类的处理逻辑
        if (wrap) {
            List> wrapperClassesList = new ArrayList<>();
            //如果该接口类型有包装类型的类
            if (cachedWrapperClasses != null) {
                wrapperClassesList.addAll(cachedWrapperClasses);
                wrapperClassesList.sort(WrapperComparator.COMPARATOR);
                Collections.reverse(wrapperClassesList);
            }
            if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
                for (Class<?> wrapperClass : wrapperClassesList) {
                    Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
                    if (wrapper == null || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
                        //实例化包装类,且对包装类进行ioc,责任链模式
                        instance = injectExtension((T)wrapperClass.getConstructor(type).newInstance(instance));
                    }
                }
            }
        }
        //扩展接口,实例化对象后可以触发方法调用
        initExtension(instance);
        return instance;
    } catch (Throwable t) {
        throw new IllegalStateException("Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: " + t.getMessage(), t);
    }
}

以Protocol接口的包装类的层级关系为例,他们是一种层级关系的结构,一层掉一层的。如图所示:

SpringCloud Alibaba系列——6Dubbo的SPI机制分析

3.4 getActivateExtension

3.4.1 方法功能

该方法是获取接口的一组实例,比如在dubbo想要获取属于生产者的所有过滤器对象,想要获取消费者的所有过滤器对象就是用这个方法获取到的。

首先看分组,如果值@Activate只配置了分组,那么就只匹配分组值 2、会匹配url中的参数,如果分组和value都配置了。首先匹配分组,然后再匹配url中的参数key

3.4.2 方法用法

/**
getActivateExtension
1、首先看分组,如果值@Activate只配置了分组,那么就只匹配分组值
2、会匹配url中的参数,如果分组和value都配置了。首先匹配分组,然后在匹配url中的参数key
*/
@Test
public void test1() {
    URL url = URL.valueOf("test://localhost/test");
    url = url.addParameter("value1","gggg");
    //只看分组,不看url中的参数
    List rabbitmq =
        ExtensionLoader.getExtensionLoader(ActivateApi.class).getActivateExtension(url, new String[]{"spring"}, "rabbitmq");
    System.out.println(rabbitmq.size());
    for (ActivateApi activateApi : rabbitmq) {
        System.out.println(activateApi.getClass());
    }
}

3.4.3 源码分析

cachedActivates也是getExtensionClasses()方法设置的全局变量

public List getActivateExtension(URL url, String[] values, String group) {
    // solve the bug of using @SPI's wrapper method to report a null pointer exception.
    Map, T> activateExtensionsMap = new TreeMap<>(ActivateComparator.COMPARATOR);
    List names = values == null ? new ArrayList<>(0) : asList(values);
    if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
        if (cachedActivateGroups.size() == 0) {
            synchronized (cachedActivateGroups) {
                // cache all extensions
                if (cachedActivateGroups.size() == 0) {
                    //这里建立名称和类的映射关系
                    getExtensionClasses();
                    // cachedActivates 名称 和 @Activate注解的映射
                    for (Map.Entry entry : cachedActivates.entrySet()) {
                        String name = entry.getKey();
                        Object activate = entry.getValue();
                        String[] activateGroup, activateValue;
                        if (activate instanceof Activate) {
                            //获取注解上的group属性
                            activateGroup = ((Activate) activate).group();
                            //获取注解上的value属性
                            activateValue = ((Activate) activate).value();
                        } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
                            activateGroup = ((com.alibaba.dubbo.common.extension.Activate)activate).group();
                            activateValue = ((com.alibaba.dubbo.common.extension.Activate)activate).value();
                        } else {
                            continue;
                        }
                        cachedActivateGroups.put(name, new HashSet<>(Arrays.asList(activateGroup)));
                        cachedActivateValues.put(name, activateValue);
                    }
                }
            }
        }
        // traverse all cached extensions
        cachedActivateGroups.forEach((name, activateGroup) -> {
            //找组匹配的组件,,如果方法传进来的参数,和类上配置的group属性是匹配的
            if (isMatchGroup(group, activateGroup)
                && !names.contains(name)
                && !names.contains(REMOVE_VALUE_PREFIX + name)
                //url中参数名称匹配,如果注解没有配置该属性则返回true,如果有配置则要匹配该方法的参数值
                && isActive(cachedActivateValues.get(name), url)) {
                //建立类和实例的映射关系
                activateExtensionsMap.put(getExtensionClass(name), getExtension(name));
            }
        });
    }
    if (names.contains(DEFAULT_KEY)) {
        // will affect order
        // `ext1,default,ext2` means ext1 will happens before all of the default extensions while ext2 will after them
        ArrayList extensionsResult = new ArrayList<>(activateExtensionsMap.size() + names.size());
        for (int i = 0; i < names.size(); i++) {
            String name = names.get(i);
            if (!name.startsWith(REMOVE_VALUE_PREFIX)
                && !names.contains(REMOVE_VALUE_PREFIX + name)) {
                if (!DEFAULT_KEY.equals(name)) {
                    if (containsExtension(name)) {
                        extensionsResult.add(getExtension(name));
                    }
                } else {
                    extensionsResult.addAll(activateExtensionsMap.values());
                }
            }
        }
        return extensionsResult;
    } else {
        // add extensions, will be sorted by its order
        for (int i = 0; i < names.size(); i++) {
            String name = names.get(i);
            if (!name.startsWith(REMOVE_VALUE_PREFIX)
                && !names.contains(REMOVE_VALUE_PREFIX + name)) {
                if (!DEFAULT_KEY.equals(name)) {
                    if (containsExtension(name)) {
                        activateExtensionsMap.put(getExtensionClass(name),getExtension(name));
                    }
                }
            }
        }
        return new ArrayList<>(activateExtensionsMap.values());
    }
}

下文预告

  1. Dubbo服务注册流程
  2. Dubbo服务发现流程
发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章