dubbo SPI @Adaptive注解使用方法与原理解析 简单易懂

@Adaptive是什么?

  • 从Dubbo官方文档可知,每个接口可以作为一个扩展点,相应的扩展类一共包含四种特性:自动包装(Wrapper)、自动加载(SPI)、自适应(Adaptive)和自动激活(Activate)。
  • @Adaptive注解可以实现扩展类的自适应属性。可以标记在类、接口、枚举类和方法上。
  • @Adaptive注解标注在接口时,首先 Dubbo 会为拓展接口生成具有代理功能的代码。然后通过javassist 或 jdk 编译这段代码,得到代理 Class 类。最后再通过反射创建代理实例。
  • @Adaptive注解标注在上不会生成代理类。被标注的扩展类将直接作为默认实现类来调用方法。在Dubbo框架中类级别的仅有AdaptiveExtensionFactoryAdaptiveCompile两个类。
  • 大部分情况下,@Adaptive 是注解在接口方法上的,真实的实现方法会在动态生成的代理类中进行抉择,接下来主要讲解方法级别的注解。

@Adaptive源码

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

@Adaptive有什么用?

举例:一个接口有三个方法,分别是methodA,methodB,methodC。此接口有三个实现类impl1,impl2,impl3。接口通过@SPI注解指定默认实现为impl1,通过@Adaptive注解及URL参数生成一个动态类,可以完成以下要求。

  • 接口能将每个方法的实现都对应不同实现类。例如接口可以让impl1调用methodA,impl2调用methodB,impl3调用methodC
  • 接口能让方法按一定优先级选择实现类来实现。例如methodA方法上有注解@Adaptive({"key1","key2","key3"})先尝试查找参数URL中key1对应的实现类,未指定则取key2,还未指定则key3,再没指定则使用SPI注解规定的默认实现类去执行方法。

@Adaptive的使用示例

接下来通过示例能更直观地看到@Adaptive的作用

示例项目结构

在这里插入图片描述

示例项目代码

com.example.demp.service.SimpleExt

impl1=com.example.demo.impl.SimpleExtImpl1
impl2=com.example.demo.impl.SimpleExtImpl2
impl3=com.example.demo.impl.SimpleExtImpl3

SimpleExt.java

package com.example.demo.service;

import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;

@SPI("impl1")
public interface SimpleExt {

    String echo(URL url,String s);

    @Adaptive({"key4"})
    void printA(URL url);

    @Adaptive
    void printB(URL url);

    @Adaptive({"key3","key2","key1"})
    void printC(URL url);
}

SimpleExtImpl1.java

package com.example.demo.impl;

import com.example.demo.service.SimpleExt;
import org.apache.dubbo.common.URL;

public class SimpleExtImpl1 implements SimpleExt {
    @Override
    public String echo(URL url, String s) {
        return null;
    }

    @Override
    public void printA(URL url) {
        System.out.println("print-A: I'm SimpleExtImpl1");
    }

    @Override
    public void printB(URL url) {
        System.out.println("print-B: I'm SimpleExtImpl1");
    }

    @Override
    public void printC(URL url) {
        System.out.println("print-C: I'm SimpleExtImpl1");
    }
}

SimpleExtImpl2.java

package com.example.demo.impl;

import com.example.demo.service.SimpleExt;
import org.apache.dubbo.common.URL;

public class SimpleExtImpl2 implements SimpleExt {
    @Override
    public String echo(URL url, String s) {
        return null;
    }

    @Override
    public void printA(URL url) {
        System.out.println("print-A: I'm SimpleExtImpl2");
    }

    @Override
    public void printB(URL url) {
        System.out.println("print-B: I'm SimpleExtImpl2");
    }

    @Override
    public void printC(URL url) {
        System.out.println("print-C: I'm SimpleExtImpl2");
    }
}

SimpleExtImpl3.java

package com.example.demo.impl;

import com.example.demo.service.SimpleExt;
import org.apache.dubbo.common.URL;

public class SimpleExtImpl3 implements SimpleExt {
    @Override
    public String echo(URL url, String s) {
        return null;
    }

    @Override
    public void printA(URL url) {
        System.out.println("print-A: I'm SimpleExtImpl3");
    }

    @Override
    public void printB(URL url) {
        System.out.println("print-B: I'm SimpleExtImpl3");
    }

    @Override
    public void printC(URL url) {
        System.out.println("print-C: I'm SimpleExtImpl3");
    }
}

测试结果

先说结论,Adaptive注解的自适应匹配遵循一定顺序。

  • 若value数组不为空,则实现类查找顺序为Adaptive注解的value数组 => spi的value
  • 若value数组为空,则实现类查找顺序为simple.ext => spi的value
  • 若查找不到所需的实现类则会抛出异常

其中simple.ext是接口(扩展点)的SimpleExt的转换,将驼峰处分开并转换成小写,然后以"."连接起来
测试代码:

URL url=URL.valueOf("dubbo://0.0.0.0:6666/test?key1=impl1&key3=impl3&simple.ext=impl2");
SimpleExt simpleExt= ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension();
simpleExt.printA(url);
simpleExt.printB(url);
simpleExt.printC(url);

url中的参数指定了

  • key1 对应impl1指代的实现类
  • key3 对应impl3指代的实现类
  • simple.ext 对应impl2指代的实现类

运行输出:
在这里插入图片描述

  • printA方法先尝试从url查找key4的value,查找不到,因此使用SPI注解中指定的impl1作为实现类
  • printB方法的Adaptive注解未指定value,因此先查找其驼峰变形名simple.ext,url中存在并对应了impl2的实现类
  • printC方法先尝试查找key3的value,发现url中存在,因此将impl3作为实现此方法的实现类
  • 注意:由于扩展点获取的是自适应实现,因此未使用@Adaptive标识的方法不应调用,调用则会抛出异常(详情可见下方动态类源码)

@Adaptive实现原理

添加了@Adaptive的接口在运行时会动态生成一个类。该类实现了一些通用逻辑,并且包含了如何选择实现类的逻辑,此类以XXX&Adaptive命名,如SimpleExt&Adaptive

  • 对于未被@Adaptive标注的方法,代理方法只会生成一句抛出异常的语句。
  • 对于有@Adaptive标注的方法,代理方法会从URL参数中提取扩展类名称,然后对扩展类顺序匹配来决定真实调用的实现类。

如下为上述接口SimpleExt.java的自动生成的动态类代码。

url.getParameter(key,default_value)用来查找URL中参数key对应的value,若无则返回default_value

package com.example.demo.service;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class SimpleExt$Adaptive implements com.example.demp.service.SimpleExt {
	public java.lang.String echo(org.apache.dubbo.common.URL arg0, String arg1)  {
throw new UnsupportedOperationException("The method public abstract void com.example.demo.service.SimpleExt.bang(org.apache.dubbo.common.URL,java.lang.String) of interface com.example.demo.service.SimpleExt is not adaptive method!");
}

	public java.lang.String printA(org.apache.dubbo.common.URL arg0)  {
		if (arg0 == null) throw new IllegalArgumentException("url == null");
		org.apache.dubbo.common.URL url = arg0;
		String extName = url.getParameter("key4", "impl1");
		if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ext1.SimpleExt) name from url (" + url.toString() + ") use keys([key4])");
		org.apache.dubbo.common.extension.ext1.SimpleExt extension = (org.apache.dubbo.common.extension.ext1.SimpleExt)ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class).getExtension(extName);
		return extension.printA(arg0);
	}


	public java.lang.String printB(org.apache.dubbo.common.URL arg0)  {
		if (arg0 == null) throw new IllegalArgumentException("url == null");
		org.apache.dubbo.common.URL url = arg0;
		String extName = url.getParameter("simple.ext", "impl1");
		if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ext1.SimpleExt) name from url (" + url.toString() + ") use keys([simple.ext])");
		org.apache.dubbo.common.extension.ext1.SimpleExt extension = (org.apache.dubbo.common.extension.ext1.SimpleExt)ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class).getExtension(extName);
		return extension.printB(arg0);
	}

	public java.lang.String printC(org.apache.dubbo.common.URL arg0)  {
		if (arg0 == null) throw new IllegalArgumentException("url == null");
		org.apache.dubbo.common.URL url = arg0;
		String extName = url.getParameter("key3", url.getParameter("key2",url.getParameter("key1","impl1")));
		if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ext1.SimpleExt) name from url (" + url.toString() + ") use keys([key3,key2,key1])");
		org.apache.dubbo.common.extension.ext1.SimpleExt extension = (org.apache.dubbo.common.extension.ext1.SimpleExt)ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class).getExtension(extName);
		return extension.printC(arg0);
	}
}

整个示例程序运行情况如下图:
在这里插入图片描述

关于源码部分在下一篇dubbo SPI @Adaptive源码解读

如果有疑问,欢迎评论~
如果成功解决了你的问题,点个赞再走吖~

©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页