【Dubbo】SPI 机制原理分析 --自适应扩展点(Adaptive)

前篇: 【Dubbo】SPI 机制原理分析 --静态扩展点

首先来看一个概念,扩展的自适应实例。如果称它为扩展代理类,可能更好理解些,扩展的自适应实例其实就是一个 Extension 的代理,它实现了扩展点接口。在调用扩展点的接口方法时,会根据实际的参数来决定要使用哪个扩展。

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

  • 如果方法参数中有 repository=mysql,那么就调用 MysqlRepository 的 save 方法。
  • 如果 repository=mongo,就调用 MongoRepository 的 save 方法。

和面向对象的延迟绑定很类似。

问题一:为什么Dubbo会引入扩展自适应实例的概念呢?

首先,Dubbo中的配置有两种。一种是固定的系统级别的配置,在 Dubbo 启动之后就不会再改了。还有一种是运行时的配置,可能对于每一次的 RPC,这些配置都不同。

第一种很好理解,这里解释一下第二种。因为 dubbo 是 url驱动,即服务的配置信息都是通过&拼接在 url 之后,换句话说,当 Provider 收到调用请求时,其相关配置是通过查 url 后的参数获得;这样做的目的是, Consumer 在注册中心拿到相应服务的 url 后,可以根据自身的配置对请求 url 再次进行拼接(修改)。因此,对于 Dubbo 而言,每一次的 RPC 调用的参数都是未知的,只有在运行时,根据这些参数才能做出正确的决定。

那么问题就来了,在 Spring bean 实例化时,如果它依赖某个扩展点,在进行依赖注入时,如何知道该使用哪个具体的扩展实现?所以,这时候就需要一个代理模式了,它实现了扩展点接口,方法内部可以根据运行时参数,动态的选择合适的扩展实现。而这个代理就是自适应实例。

自适应扩展实例在 Dubbo 中的使用非常广泛,Dubbo中,每一个扩展都会有一个自适应类,如果我们没有提供,Dubbo会使用字节码工具为我们自动生成一个。所以我们基本感觉不到自适应类的存在。

问题二:如何标识自适应扩展呢?

@Adaptive 就是一个自适应扩展点的标识。它可以修饰在类上,也可以修饰在方法上面。

类级别:

  • 用于具体扩展
  • 代表实现一个装饰类,类似于设计模式中的装饰模式,它主要作用是返回指定类
  • 目前在整个系统中 AdaptiveCompiler、AdaptiveExtensionFactory 这两个类拥有该注解

方法级别:

  • 用于扩展点(接口)中的方法
  • 表示需要生成一个动态代理,方法内部会根据方法的参数,来决定使用哪个扩展

PS:说简单点,如果作用在类上,就相当于自己定义了个现成的代理;如果作用在方法上,运行时会动态生成一个 Adaptive 实例。

1.代码示例

1.1 类级别

示例,传入扩展点(Compiler 接口),返回 AdaptiveCompiler。

// 注意,使用自适应模式时,调用的方法是 getAdaptiveExtension(),不是 getExtension(key)。
Compiler compiler = ExtensionLoader.getExtensionLoader(Compiler.class).getAdaptiveExtension(); 
System.out.println(compiler.getClass()); 

在这里插入图片描述

问题一:getAdaptiveExtension() 为什么是返回 AdaptiveCompiler 呢?

我们来看看 META-INF/dubbo/internal/org.apache.dubbo.common.compiler.Compiler

在这里插入图片描述
AdaptiveCompiler 被加载就是因为第一行的原因?错,是因为 getAdaptiveExtension() 表示获取适配器。

问题二:AdaptiveCompiler 有什么用?

打开 AdaptiveCompiler 类,看到这个类上面有一个注解@Adaptive。

在这里插入图片描述

=> AdaptiveCompiler其实就是个适配器,适配的是 JdkCompiler 和 JavassistCompiler(真正提供扩展点实现)

问题三:那这两种实现,到底该使用哪种?或者说如何适配的?

@Adaptive
public class AdaptiveCompiler implements Compiler {

    private static volatile String DEFAULT_COMPILER;

    public static void setDefaultCompiler(String compiler) {
        DEFAULT_COMPILER = compiler;
    }
	
	// 关注compile方法,看他调用jdkCompiler还是javassistCompiler的compile方法
    @Override 
    public Class<?> compile(String code, ClassLoader classLoader) {
        Compiler compiler;
        ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
        // name
        String name = DEFAULT_COMPILER;  
        // 首先根据name进行选择
        if (name != null && name.length() > 0) {
            compiler = loader.getExtension(name); 
        // 否则加载默认扩展点实现
        } else {
            compiler = loader.getDefaultExtension(); 
        }
        return compiler.compile(code, classLoader);
    }
}

在这里插入图片描述

1.2 方法级别

比如 Protocol 接口,它里面定义了 export 和 refer 两个抽象方法,这两个方法分别带有@Adaptive 的标识,标识是一个自适应方法。

Protocol 是一个通信协议接口,具体有多种实现,谁来执行这个选择呢?答:运行时生成的动态代理 Protocol$Adaptive

在这里插入图片描述
可以看到,如果 url 中没有携带 Protocol 设置,就采用默认的 dubbo 协议,然后再通过 ExtensionLoader 去获取具体的扩展实例(到这到这里其实就是静态扩展点了)。
在这里插入图片描述
这里的方法层面的 Adaptive 就决定了当前这个方法会采用何种协议来发布服务。

2.源码分析

其中原理从 getAdaptiveExtension() 开始分析

getAdaptiveExtension()

public T getAdaptiveExtension() {
    // cachedAdaptiveInstance是一个缓存,在dubbo中大量用到了这种内存缓存
    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("Failed to create adaptive instance: " + t.toString(), t);
                    }
                }
            }
        } else {
            throw new IllegalStateException("Failed to create adaptive instance: " + 
                          createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
        }
    }

    return (T) instance;
}

createAdaptiveExtension()

private T createAdaptiveExtension() {
        try {
        	// 1.获得一个自适应扩展点实例
        	// 2.进行依赖注入
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }

getAdaptiveExtensionClass()

private Class<?> getAdaptiveExtensionClass() {
        getExtensionClasses();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }

cachedAdaptiveClass 是一个类级别自适应扩展点,表示告诉 dubbo spi loader,“我是一个自适应扩展点,来加载我吧”。

cachedAdaptiveClass 应该是在加载解析 /META-INF/dubbo 下的扩展点的时候加载进来的。在加载完之后如果这个类有@Adaptive 标识,则会赋值赋值而给 cachedAdaptiveClass

如果 cachedAdaptiveClass 不存在,dubbo 会动态生成一个代理类 Protocol$Adaptive.(前面的名字 protocol 是根据当前 ExtensionLoader 所加载的扩展点来定义的)

createAdaptiveExtensionClass()

动态生成字节码,然后进行动态加载。如果加载的是 Protocol.class,应该是 Protocol$Adaptive

private Class<?> createAdaptiveExtensionClass() {
    // 生成动态代理的类的字符串
    // 这个 cachedDefaultName 实际上就是扩展点接口的@SPI 注解对应的名字
    // 如果此时加载的是 Protocol.class,那么 cachedDefaultName=dubbo 
    String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
    ClassLoader classLoader = findClassLoader();
    org.apache.dubbo.common.compiler.Compiler compiler = 
        ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).
        getAdaptiveExtension();
    return compiler.compile(code, classLoader);
}

injectExtension()

对于扩展点进行依赖注入,简单来说就是如果当前加载的扩展点中存在一个成员属性(对象),并且提供了 set 方法,那么这个方法就会执行依赖注入

private T injectExtension(T instance) {
        try {
            if (objectFactory != null) { // objectFactory这里用到了
                // 获得实例对应的方法,判断方法是否是一个set方法
                for (Method method : instance.getClass().getMethods()) {
                    if (isSetter(method)) {
     				   // 可以选择禁用依赖注入
                        if (method.getAnnotation(DisableInject.class) != null) {
                            continue;
                        }
                        // 获得方法的参数,这个参数必须是一个对象类型并且是一个扩展点  
                        Class<?> pt = method.getParameterTypes()[0]; // 获得这个方法的参数类型
                        if (ReflectUtils.isPrimitives(pt)) { // 如果不是对象类型,就跳过
                            continue;
                        }
                        try {
                             // 获得这个方法的属性名称
                            String property = getSetterProperty(method);
                            // 根据class以及name,使用自适应扩展点进行加载,并赋值到当前set方法中
                            Object object = objectFactory.getExtension(pt, property);
                            if (object != null) {
                                // 调用set方法进行赋值
                                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;
    }

在 injectExtension 这个方法中,入口处的代码首先判断了 objectFactory 这个对象是否为空。这个是在哪里初始化的呢? 实际上在获得 ExtensionLoader 的时候,就对 objectFactory 进行了初始化。

在这里插入图片描述
然后通过 ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()去获得一个自适应的扩展点。进入 ExtensionFactory 这个接口,可以看到它是一个扩展点

在这里插入图片描述

并且有一个自己实现的自适应扩展点 AdaptiveExtensionFactory

在这里插入图片描述

/**
*@Adaptive 加载到类上表示这是一个自定义的适配器类,表示我们再调用 getAdaptiveExtension 方法的时候,
*不需要走上面这么复杂的过程。会直接加载到 AdaptiveExtensionFactory。
*然后在 getAdaptiveExtensionClass()方法处有判断 
*/
@Adaptive
public class AdaptiveExtensionFactory implements 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);
    }

    @Override
    // 我们可以看到除了自定义的自适应适配器类以外,还有两个实现类,一个是 SPI,一个是 Spring,
	// 轮询这 2 个,从一个中获取到就返回。 
    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;
    }

}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A minor

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值