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相关内容不在本文讨论范围

相关文章
|
1月前
|
负载均衡 监控 Dubbo
Dubbo 原理和机制详解(非常全面)
本文详细解析了 Dubbo 的核心功能、组件、架构设计及调用流程,涵盖远程方法调用、智能容错、负载均衡、服务注册与发现等内容。欢迎留言交流。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
Dubbo 原理和机制详解(非常全面)
|
4月前
|
负载均衡 Dubbo 应用服务中间件
Dubbo服务调用过程原理
该文章主要介绍了Dubbo服务调用过程的原理,包括服务调用的主要阶段和服务调用的具体步骤。
Dubbo服务调用过程原理
|
4月前
|
缓存 Dubbo Java
Dubbo服务消费者启动与订阅原理
该文章主要介绍了Dubbo服务消费者启动与订阅的原理,包括服务消费者的启动时机、启动过程以及订阅和感知最新提供者信息的方式。
Dubbo服务消费者启动与订阅原理
|
4月前
|
Dubbo 网络协议 Java
深入掌握Dubbo服务提供者发布与注册原理
该文章主要介绍了Dubbo服务提供者发布与注册的原理,包括服务发布的流程、多协议发布、构建Invoker、注册到注册中心等过程。
深入掌握Dubbo服务提供者发布与注册原理
|
4月前
|
缓存 负载均衡 Dubbo
Dubbo服务集群容错原理(重要)
该文章主要介绍了Dubbo服务集群容错的原理,包括集群容错技术的概念、Dubbo中使用的集群容错技术种类及其原理。
|
7月前
|
Dubbo Java 应用服务中间件
微服务学习 | Springboot整合Dubbo+Nacos实现RPC调用
微服务学习 | Springboot整合Dubbo+Nacos实现RPC调用
|
2月前
|
Dubbo Java 应用服务中间件
Spring Cloud Dubbo:微服务通信的高效解决方案
【10月更文挑战第15天】随着信息技术的发展,微服务架构成为企业应用开发的主流。Spring Cloud Dubbo结合了Dubbo的高性能RPC和Spring Cloud的生态系统,提供高效、稳定的微服务通信解决方案。它支持多种通信协议,具备服务注册与发现、负载均衡及容错机制,简化了服务调用的复杂性,使开发者能更专注于业务逻辑的实现。
74 2
|
4月前
|
Dubbo Java 应用服务中间件
💥Spring Cloud Dubbo火爆来袭!微服务通信的终极利器,你知道它有多强大吗?🔥
【8月更文挑战第29天】随着信息技术的发展,微服务架构成为企业应用开发的主流模式,而高效的微服务通信至关重要。Spring Cloud Dubbo通过整合Dubbo与Spring Cloud的优势,提供高性能RPC通信及丰富的生态支持,包括服务注册与发现、负载均衡和容错机制等,简化了服务调用管理并支持多种通信协议,提升了系统的可伸缩性和稳定性,成为微服务通信领域的优选方案。开发者仅需关注业务逻辑,而无需过多关心底层通信细节,使得Spring Cloud Dubbo在未来微服务开发中将更加受到青睐。
90 0
|
1月前
|
Dubbo Cloud Native 应用服务中间件
阿里云的 Dubbo 和 Nacos 深度整合,提供了高效的服务注册与发现、配置管理等关键功能,简化了微服务治理,提升了系统的灵活性和可靠性。
在云原生时代,微服务架构成为主流。阿里云的 Dubbo 和 Nacos 深度整合,提供了高效的服务注册与发现、配置管理等关键功能,简化了微服务治理,提升了系统的灵活性和可靠性。示例代码展示了如何在项目中实现两者的整合,通过 Nacos 动态调整服务状态和配置,适应多变的业务需求。
43 2
|
2月前
|
Dubbo Java 应用服务中间件
Dubbo学习圣经:从入门到精通 Dubbo3.0 + SpringCloud Alibaba 微服务基础框架
尼恩团队的15大技术圣经,旨在帮助开发者系统化、体系化地掌握核心技术,提升技术实力,从而在面试和工作中脱颖而出。本文介绍了如何使用Dubbo3.0与Spring Cloud Gateway进行整合,解决传统Dubbo架构缺乏HTTP入口的问题,实现高性能的微服务网关。