Dubbo-自适应扩展机制之Adaptive注解原理

简介: Dubbo-自适应扩展机制之Adaptive注解原理

什么是自适应机制

上篇讲解Dubbo SPI机制的文章中提到,Dubbo里可以通过ExtensionLoader.getExtensionLoader(XXXClass).getExtension(key)的形式来获取接口的某个实现类。

但这种形式本质上还是通过硬编码的形式在代码中固定的获取了接口的一个实现,诸如Protocol(实现有Dubbo、Redis、Thrift等),或者Transporter(实现有Netty、Mina等)这些接口,我们是可以在Dubbo服务声明时指定具体实现的。如图所示:

那么Dubbo是如何实现根据用户指定的参数来找到对应的接口实现类的呢?

先给出结论:Dubbo接口在拓展方法被调用时,根据运行时参数(URL中参数)动态生成接口的Adaptive自适应拓展类,在生成的拓展方法中根据URL中用户指定的参数key,调用

ExtensionLoader.getExtensionLoader(XXXClass).getExtension(key)来获取具体的实现类


那么上面提到的运行时动态生成的拓展类代码是什么样的呢?以Protocol接口为例


先看下包含自适应方法的Protocol接口定义,export,refer是2个自适应的方法(用 @Adaptive注解修饰)

@SPI("dubbo")
public interface Protocol {
    int getDefaultPort();
    @Adaptive
    <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
    @Adaptive
    <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
    void destroy();
}

Protocol接口生成的自适应扩展类,类名为Protocol$Adaptive,代码如下

package com.alibaba.dubbo.rpc;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive 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);
    }
}

普通方法getDefaultPortdestroy在自适应拓展类的实现中,直接抛出异常

自适应方法,如refer方法,内部基本实现思路有两点

  1. 从URL中获取接口实现extName
  1. 根据extName调用ExtensionLoader.getExtension(extName)获取实现类

先从URL中获取拓展类实现的名称extName(Protocal的默认SPI实现为Dubbo协议)

String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());

然后根据参数中的extName加载最终实现类,调用实现类的refer方法

com.alibaba.dubbo.rpc.Protocol extension =
                (com.alibaba.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.rpc.Protocol.class).getExtension(extName)

看到这,可能大家对自适应机制已经有个大概的了解了,其关键就是自适应类的生成和Dubbo SPI中ExtensionLoader的使用。下面先从注解入手,开始分析自适应机制的原理。

@Adaptive注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
    String[] value() default {};
}

@Adaptive注解可以作用在类上、接口上。


1.作用在类上:在Dubbo中只有2个类上有Adaptive注解修饰,分别是 AdaptiveCompiler 和 AdaptiveExtensionFactory,表示拓展的加载逻辑由人工编码完成,即硬编码实现,举个例子,AdaptiveCompiler的comiple方法不需要在运行时根据URL中参数动态生成,直接代码中固定写死

20191028154055503.png

2.作用在接口上:如上文提到的,Adaptive注解作用在接口上时,能在程序运行时,根据参数(URL中参数)动态生成接口的Adaptive自适应拓展类,下面针对此种情况做具体分析

ExtensionLoader#getAdaptiveExtension

自适应拓展类加载时,调用的是ExtensionLoader#getAdaptiveExtension方法

自适应类相关缓存定义

//缓存的自适应扩展类实例
private final Holder<Object> cachedAdaptiveInstance = new Holder<Object>();
//缓存的自适应扩展类
private volatile Class<?> cachedAdaptiveClass = null;

代码如下,同样的先从缓存查询,没有再创建

public T getAdaptiveExtension() {
     //查询缓存
        Object instance = cachedAdaptiveInstance.get();
        //缓存中没有,双重检查锁定
        if (instance == null) {
            if (createAdaptiveInstanceError == null) {
                synchronized (cachedAdaptiveInstance) {
                    instance = cachedAdaptiveInstance.get();
                    if (instance == null) {
                        try {
                          //调用createAdaptiveExtension创建自适应拓展类的实例
                            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;
    }

createAdaptiveExtension方法如下

private T createAdaptiveExtension() {
        try {
            return injectExtension((T) getAdaptiveExtensionClass().newInstance());
        } catch (Exception e) {
            throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
        }
    }

做的事情比较简单,分为如下几步,重点是getAdaptiveExtensionClass方法

  1. 调用getAdaptiveExtensionClass方法获取自适应扩展类Class
  2. 调用反射创建自适应扩展类实例
  1. 为实例调用injectExtension注入其依赖的对象

getAdaptiveExtensionClass方法如下

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

分为两步

  1. getExtensionClasses获取所有接口的SPI扩展类实现,比如Protolcol接口的实现类有:

同时,如果Adaptive注解被修饰在类上,会将该类加入cachedAdaptiveClass缓存中,即上面提到的AdaptiveCompiler 和 AdaptiveExtensionFactory不会再动态生成自适应拓展类,直接返回缓存中的Class



2.第二步就是调用createAdaptiveExtensionClass方法创建自适应拓展类,代码如下

private Class<?> createAdaptiveExtensionClass() {

//动态生成自适应拓展类代码(字符串拼装)

String code = createAdaptiveExtensionClassCode();

ClassLoader classLoader = findClassLoader();

//调用JavaAssist,Jdk等编译器对生成的代码进行编译

com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();

return compiler.compile(code, classLoader);

}


如注释里说的,首先拼装出自适应拓展类的代码,再调用编译器编译代码,生成Class对象

自适应代码生成

createAdaptiveExtensionClassCode代码较长,但最终生成结果不复杂,Protocal接口生成的自适应拓展类代码Protocol$Adaptive已经在文章开头贴过了,就不重复贴了,只讲解代码中几个关键步骤

  1. 校验方法中是否有Adaptive注解,没有则抛出异常

  2. 导入package包信息

    实现类只依赖ExtensionLoader
  3. 普通方法不生成拓展方法

普通方法的拓展方法如下


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!");

}


4.else逻辑中的实现即为具体自适应拓展方法的实现,具体实现有几个关键,一是根据URL及Adaptive中注解定义的key值获取具体的实现类的key值,如果Adaptive注解中没有指定value,则获取SPI注解中的默认值,什么意思呢?以Transporter#connect的自适应实现为例


优先从URL中获取Adaptive中指定value的拓展实现,获取不到时,再获取SPI中指定的默认实现

从URL获得了extName时,拼装出

ExtensionLoader.getExtensionLoader(XXXClass).getExtension(extName),达到根据参数配置来调用具体实现的效果,即如图所示代码

自适应代码编译

到上面为止,动态生成的代码已经有了,但是要在JVM里运行,光有字符串形式的代码是不够的,需要对代码进行编译处理

ClassLoader classLoader = findClassLoader();
        com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);

Compiler本身也是一个自适应实现,默认是JavaAssist的实现,具体实现在JavassistCompiler中,有兴趣的自己看,JavaAssist相关内容不在本文讨论范围

相关文章
|
3月前
|
Dubbo Java 应用服务中间件
Dubbo服务暴露机制解密:深入探讨服务提供者的奥秘【九】
Dubbo服务暴露机制解密:深入探讨服务提供者的奥秘【九】
25 0
|
4月前
|
缓存 Dubbo Java
Dubbo 第三节_ Dubbo的可扩展机制SPI源码解析
Dubbo会对DubboProtocol对象进⾏依赖注⼊(也就是⾃动给属性赋值,属性的类型为⼀个接⼝,记为A接⼝),这个时候,对于Dubbo来说它并不知道该给这个属性赋什么值,换句话说,Dubbo并不知道在进⾏依赖注⼊时该找⼀个什么的的扩展点对象给这个属性,这时就会预先赋值⼀个A接⼝的⾃适应扩展点实例,也就是A接⼝的⼀个代理对象。在调⽤getExtension去获取⼀个扩展点实例后,会对实例进⾏缓存,下次再获取同样名字的扩展点实例时就会从缓存中拿了。Protocol是⼀个接。但是,不是只要在⽅法上加了。
|
14天前
|
负载均衡 监控 Dubbo
秒懂Dubbo接口(原理篇)
【4月更文挑战第25天】秒懂Dubbo接口(原理篇)
27 3
秒懂Dubbo接口(原理篇)
|
1月前
|
设计模式 JSON Dubbo
超越接口:探索Dubbo的泛化调用机制
超越接口:探索Dubbo的泛化调用机制
28 0
|
2月前
|
Java fastjson 数据安全/隐私保护
【Dubbo3技术专题】「云原生微服务开发实战」 一同探索和分析研究RPC服务的底层原理和实现
【Dubbo3技术专题】「云原生微服务开发实战」 一同探索和分析研究RPC服务的底层原理和实现
44 0
|
2月前
|
Dubbo Cloud Native 应用服务中间件
【Dubbo3 终极特性】「云原生三中心架构」带你探索 Dubbo3 体系下的配置中心和元数据中心、注册中心的原理及开发实战(中)
【Dubbo3 终极特性】「云原生三中心架构」带你探索 Dubbo3 体系下的配置中心和元数据中心、注册中心的原理及开发实战(中)
28 1
|
2月前
|
Dubbo 网络协议 应用服务中间件
分布式微服务框架dubbo原理与机制
分布式微服务框架dubbo原理与机制
|
3月前
|
Dubbo Java 应用服务中间件
Dubbo 第四节: Spring与Dubbo整合原理与源码分析
DubboConfigConfigurationRegistrar的主要作⽤就是对propties⽂件进⾏解析并根据不同的配置项项⽣成对应类型的Bean对象。
|
3月前
|
XML 负载均衡 Dubbo
了解Dubbo配置:优先级、重试和容错机制的秘密【五】
了解Dubbo配置:优先级、重试和容错机制的秘密【五】
41 0
|
16天前
|
Dubbo Java 应用服务中间件
微服务学习 | Springboot整合Dubbo+Nacos实现RPC调用
微服务学习 | Springboot整合Dubbo+Nacos实现RPC调用