Dubbo扩展加载(二)

一、Dubbo SPI加载机制,Dubbo扩展加载分为3类,普通扩展加载、自适应扩展加载,批量扩展加载。

Dubbo SPI和JDK SPI相比较:(dubbo官网描述

Dubbo 的扩展点加载从 JDK 标准的 SPI (Service Provider Interface) 扩展点发现机制加强而来。

Dubbo 改进了 JDK 标准的 SPI 的以下问题:

  • JDK 标准的 SPI 会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
  • 如果扩展点加载失败,连扩展点的名称都拿不到了。比如:JDK 标准的 ScriptEngine,通过 getName() 获取脚本类型的名称,但如果 RubyScriptEngine 因为所依赖的 jruby.jar 不存在,导致 RubyScriptEngine 类加载失败,这个失败原因被吃掉了,和 ruby 对应不起来,当用户执行 ruby 脚本时,会报不支持 ruby,而不是真正失败的原因。
  • 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接 setter 注入其它扩展点。

二、普通扩展加载:

1、普通扩展简单应用:

修改上一章中接口,在接口Animal上加上@SPI注解。

package com.coke.spi;

import com.alibaba.dubbo.common.extension.SPI;

@SPI("cat")
public interface Animal {

    void run();
}

修改配置文件。

运行测试。

package com.coke.spi;

import com.alibaba.dubbo.common.extension.ExtensionLoader;

public class DubboSpiTest {


    public static void main(String[] args) {

        Animal animal = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("dog");

        animal.run();

    }
}

运行结果。

dog run

Process finished with exit code 0

从获取扩展点的方式,我们可以发现,dubbo扩展点是获取特定实例,并不需要实力化所有实现类。

源码分析:

从ExtentionLoader.getExtension(String name)这个函数开始。

1、首先从缓存中获取扩展类实例,如果获取不到才开始创建。

    public T getExtension(String name) {
        if (StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Extension name == null");
        }
        if ("true".equals(name)) {
            return getDefaultExtension();
        }
        //从缓存获取
        final Holder<Object> holder = getOrCreateHolder(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;
    }

接下来看扩展点的创建。

1、首先获取扩展点的Class类,先从缓存获取,没有获取到就用ClassLoader去加载根目录下META-INF/services/、META-INF/services/、META-INF/services/internal/三个位置的配置文件,通过BufferReader一行一行读取并通过Class.forname加载Class类,将加载好的Class类放入缓存。

2、获取到Class后,先查看缓存中是否有实例,有就直接获取,没有通过反射创建实例,并缓存。

    private T createExtension(String name) {
//获取扩展点的Class类
        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, clazz.newInstance());
                instance = (T) EXTENSION_INSTANCES.get(clazz);
            }
//inject注入,类似于spring IOC
            injectExtension(instance);
            Set<Class<?>> wrapperClasses = cachedWrapperClasses;
            if (CollectionUtils.isNotEmpty(wrapperClasses)) {
                for (Class<?> wrapperClass : wrapperClasses) {
//注入到包装类型
                    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);
        }
    }

    private Map<String, Class<?>> loadExtensionClasses() {
        cacheDefaultExtensionName();

        Map<String, Class<?>> extensionClasses = new HashMap<>();
//加载策略,分别是三个文件位置,META-INF/services/;META-INF/services/;META-INF/services/
internal/

        for (LoadingStrategy strategy : strategies) {
            loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.excludedPackages());
//兼容com.alibaba的包
            loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.excludedPackages());
        }

        return extensionClasses;
    }

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type,
                               boolean extensionLoaderClassLoaderFirst, String... excludedPackages) {
        String fileName = dir + type;
        try {
            Enumeration<java.net.URL> urls = null;
            ClassLoader classLoader = findClassLoader();

            // try to load from ExtensionLoader's ClassLoader first
            if (extensionLoaderClassLoaderFirst) {
                ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
                if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
                    urls = extensionLoaderClassLoader.getResources(fileName);
                }
            }

            if(urls == null || !urls.hasMoreElements()) {
                if (classLoader != null) {
//直接通过classLoader加载资源
                    urls = classLoader.getResources(fileName);
                } else {
                    urls = ClassLoader.getSystemResources(fileName);
                }
            }

            if (urls != null) {
                while (urls.hasMoreElements()) {
                    java.net.URL resourceURL = urls.nextElement();
                    loadResource(extensionClasses, classLoader, resourceURL, excludedPackages);
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", description file: " + fileName + ").", t);
        }

三、自适应加载。

自适应加载不通于普通加载,自适应加载是在类运行的时候,通过运行中的参数,动态的选择实现类。

1、简单实例

修改Animal接口,下周eat方法,参数为com.alibaba.dubbo.common.URL,在方法上添加@Adaptive注解

package com.coke.spi;

import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.Adaptive;
import com.alibaba.dubbo.common.extension.SPI;

@SPI("cat")
public interface Animal {

    void run();

    @Adaptive(value = "animal")
    void eat(URL url);
}

运行测试,新怎URL并设置animal属性为cat

package com.coke.spi;

import com.alibaba.dubbo.common.URL;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
import com.alibaba.dubbo.common.utils.UrlUtils;

public class DubboSpiTest {


    public static void main(String[] args) {

//        Animal animal = ExtensionLoader.getExtensionLoader(Animal.class).getExtension("dog");
//
//        animal.run();

        Animal animal = ExtensionLoader.getExtensionLoader(Animal.class).getAdaptiveExtension();

        URL url = URL.valueOf("url");
        url.addParameter("animal", "cat");
        animal.eat(url);

    }
}

运行结果:属性缓存dog,运行结果为dog eat

cat eat

Process finished with exit code 0


源码分析:

从ExtensionLoader.getAdaptiveExtension开始

1、类似于普通扩展加载,首先从缓存中获取,缓存中没有就创建。

    public T getAdaptiveExtension() {
//从缓存中获取
        Object instance = this.cachedAdaptiveInstance.get();
        if (instance == null) {
            if (this.createAdaptiveInstanceError != null) {
                throw new IllegalStateException("fail to create adaptive instance: " + this.createAdaptiveInstanceError.toString(), this.createAdaptiveInstanceError);
            }

            Holder var2 = this.cachedAdaptiveInstance;
            synchronized(this.cachedAdaptiveInstance) {
                instance = this.cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
//缓存中没有就创建
                        instance = this.createAdaptiveExtension();
                        this.cachedAdaptiveInstance.set(instance);
                    } catch (Throwable var5) {
                        this.createAdaptiveInstanceError = var5;
                        throw new IllegalStateException("fail to create adaptive instance: " + var5.toString(), var5);
                    }
                }
            }
        }

        return instance;
    }

2、获取自适应扩展类的Class类,从代码可以看出先通过createAdaptiveExtensionClassCode函数生成代码code,并通过编译器重新编译后获取Class对象,这里其实是用到了动态代理的设计模式,通过createAdaptiveExtensionClassCode组合出代理类的代码String字符串,并重新编译。

    private Class<?> createAdaptiveExtensionClass() {
//代码重组
        String code = this.createAdaptiveExtensionClassCode();
        ClassLoader classLoader = findClassLoader();
//重新编译
        Compiler compiler = (Compiler)getExtensionLoader(Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }

可以通过工具获取到重新编译后的类,如在dubbo中有个Cluster类,首先会以@Adaptive注解中的参数作为key去获取Url中对应的属性名,然后通过普通扩展加载机制获取具体的实现类,最终执行交给加载的类来执行。

package org.apache.dubbo.demo.consumer;

import org.apache.dubbo.common.extension.ExtensionLoader;

public class Cluster$Adaptive implements org.apache.dubbo.rpc.cluster.Cluster {
    public org.apache.dubbo.rpc.Invoker join(org.apache.dubbo.rpc.cluster.Directory arg0) throws org.apache.dubbo.rpc.RpcException {
        if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.cluster.Directory argument == null");
        if (arg0.getUrl() == null)
            throw new IllegalArgumentException("org.apache.dubbo.rpc.cluster.Directory argument getUrl() == null");
        org.apache.dubbo.common.URL url = arg0.getUrl();
        String extName = url.getParameter("cluster", "failover");
        if (extName == null)
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.cluster.Cluster) name from url (" + url.toString() + ") use keys([cluster])");
//获取扩展实例
        org.apache.dubbo.rpc.cluster.Cluster extension = (org.apache.dubbo.rpc.cluster.Cluster) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.cluster.Cluster.class).getExtension(extName);
//最终转由具体类执行
        return extension.join(arg0);
    }
}

四、批量扩展加载:

批量扩展加载类似于普通扩展加载,不同点是,普通扩展加载每次只加载一个类,而批量加载会根据url中的key去匹配多个扩展点实现类,返回匹配成功的扩展点

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值