Dubbo 2.7.3源码分析——Dubbo SPI(二)

老板,救救孩子?关注一下吧!

Dubbo 2.7.3源码分析——Dubbo SPI(二)_第1张图片



作者 肥又君

前情回顾

在前面介绍了Dubbo SPI 中 ExtensionLoader 的基础使用和原理,本篇重点介绍 Dubbo SPI 对 @Adaptive 注解的处理。

@Adaptive注解简介

@Adaptive 注解是什么东西?

通过名字就能看出,它是用来标注适配器的。Dubbo SPI能根据配置选择实现类了,那还要适配器干啥呢?

简单点说,就是动态选择实现,META-INF 目录下面的配置毕竟还是静态的,要是能根据参数选择实现,那就爽歪歪了。

@Adaptive 注解可以标注在类上,也可以标注在方法上。这里直接先摆出 @Adaptive 注解的功能:

  1. 当 @Adaptive 注解直接标记适配器类上的时候,优先级最高,Dubbo直接使用该类作为接口实现。

  2. 当 @Adaptive 注解标记在方法上时,Dubbo 会解析该方法上的URL类型参数,确定该方法使用的具体实现类。

对于2,有几点说明,一个是这里的URL是org.apache.dubbo.common.URL,先把它理解成一个KV就行,【整个Dubbo系列中,只要没有特殊说明,URL都是指的 org.apache.dubbo.common.URL ,千万别和java.net.URL混了,混了的都是傻】。

另一个要想动态选择实现,就必须要有代理,代理类是跑不掉的,既然我们啥都没干,那肯定是 Dubbo 帮我们做了,后面的代码会详细说。

Dubbo 2.7.3源码分析——Dubbo SPI(二)_第2张图片

@Adaptive 注解示例

这个示例重点展示上述第2点的效果,涉及一个接口—— Car,两个实现类—— BMW 和 Audi,还有一个 SPI 配置文件,当然还有一个 Main 方法。

 
   

首先,我们先定义 Car 接口:

@SPI("BMW") // @SPI注解表示该接口是个扩展点	
public interface Car {	
    // 看到第一个URL参数了吗?@Adaptive会告诉Dubbo SPI	
    // 生成的代理类,解析这个URL参数,根据解析到的type参数	
    // 确定start()方法使用哪个实现类。如果没有type参数,	
    // 则尝试通过model参数确定实现类	
    @Adaptive({"type", "model"})	
    void start(URL url, String who);	
	
    @Adaptive({"type", "model"})	
    void stop(URL url, String who);	
}

接下来是两个平淡无奇,但是想坐在里面哭的实现类—— BMW 和 Audi,醒醒,看代码吧

 
   
public class BMW implements Car{	
    @Override	
    public void start(URL url, String who) {	
        System.out.println(who+" is starting the BMW ...");	
    }	
	
    @Override	
    public void stop(URL url, String who) {	
        System.out.println(who+" is stoping the BMW ...");	
    }	
}	
	
public class Audi implements Car{	
    @Override	
    public void start(URL url, String who) {	
        System.out.println(who+" is starting the Audi ...");	
    }	
	
    @Override	
    public void stop(URL url, String who) {	
        System.out.println(who+" is stoping the Audi ...");	
    }	
}

接下来是 org.apache.dubbo.common.extension.test.Car 配置文件,注意哦,这个文件是放到 resources/META-INF/dubbo/internal 目录下的,为什么到这里?翻翻关注公众号翻翻上一篇对ExtensionLoader的分析吧。

 
   
BMW=org.apache.dubbo.common.extension.test.BMW	
Audi=org.apache.dubbo.common.extension.test.Audi

最后来看Main 方法: 

 
   
public static void main(String[] args) throws Exception {	
    // 这里调用的是getAdaptiveExtension()方法,而不是	
    // 上一篇介绍的getExtension()方法哦!	
    Car car = 	
        ExtensionLoader	
        .getExtensionLoader(Car.class)	
        .getAdaptiveExtension();	
	
    // 注意这个URL,没有type参数也没有model参数,Dubbo会根据Car接口@SPI注解中	
    // 指定的默认值选择实现类	
    URL url = URL.valueOf("test://mycar");	
    car.start(url, "黄宏斯坦森");	
    // 输出:黄宏斯坦森 is starting the BMW ...	
	
    // 优先根据URL中的type参数选择实现	
    URL startUrl = URL.valueOf("test://mycar?type=BMW&model=Audi");	
    car.start(startUrl, "肥又君");	
    // 输出:肥又君 is starting the BMW ...	
	
    // 没有type参数根据URL中的model选择实现	
    URL stopUrl = URL.valueOf("test://mycar?model=Audi");	
    car.stop(stopUrl, "百里");	
    // 百里 is stoping the Audi ...	
}


Dubbo 2.7.3源码分析——Dubbo SPI(二)_第3张图片

@Adaptive 注解实现原理

前面说了,一个Car对象会根据URL参数切换具体的实现主要是因为Dubbo SPI会生成一个Adaptive的代理类。下面就来分析 ExtensionLoader.getAdaptiveExtension() 方法的如何处理@Adaptive注解以及生成Adaptive代理的。

 
   

先来看 ExtensionLoader.getAdaptiveExtension() 方法的实现:

// cachedAdaptiveInstance又是一个Holder,用来缓存Adaptive代理类	
private final Holder cachedAdaptiveInstance = new Holder<>();	
	
public T getAdaptiveExtension() {	
    // 每个SPI接口最多就只能有一个Adaptive代理对象,这里先查缓存	
    Object instance = cachedAdaptiveInstance.get();	
    // double-check处理并发问题	
    if (instance == null) {	
        synchronized (cachedAdaptiveInstance) {	
            instance = cachedAdaptiveInstance.get();	
            if (instance == null) {	
                // 查找并创建Adaptive代理类实例	
                instance = createAdaptiveExtension();	
                cachedAdaptiveInstance.set(instance); // 设置缓存	
            }	
        }   	
    }	
    return (T) instance;	
} 
   

下面来看 createAdaptiveExtension() 方法的核心流程:

 
   
private volatile Class cachedAdaptiveClass = null;	
	
private void loadClass(Map> extensionClasses, 	
        java.net.URL resourceURL,	
        Class clazz, String name) throws NoSuchMethodException {	
    ... ... 	
    // 如果发现一个实现类被@Adaptive注解标记了,则记录到cachedAdaptiveClass字段,	
    // 这个字段后面有用	
    if (clazz.isAnnotationPresent(Adaptive.class)) {	
        cacheAdaptiveClass(clazz); 	
    } else 	
    ... ...	
}
 
   
private Class createAdaptiveExtensionClass() {	
    // 生成Adaptive代理类,返回的code就是生成的Java代码	
    String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();	
    // 获取ClassLoader	
    ClassLoader classLoader = findClassLoader();	
    // 通过SPI方式获取Compiler实现类	
    org.apache.dubbo.common.compiler.Compiler compiler = 	
            ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class)	
                    .getAdaptiveExtension();	
    // 编译并加载上面生成的Java代码	
    return compiler.compile(code, classLoader);	
}

Dubbo 2.7.3源码分析——Dubbo SPI(二)_第4张图片

简单看看上面示例生成的 Adaptive 代理 —— Car$Adaptive:

 
   
public class Car$Adaptive implements org.apache.dubbo.common.extension.test.Car {	
    public void start(org.apache.dubbo.common.URL arg0, java.lang.String arg1) {	
        if (arg0 == null) throw new IllegalArgumentException("url == null");	
        org.apache.dubbo.common.URL url = arg0;	
        // 尝试先尝试获取type,然后尝试获取model,最后再用"BMW"默认值	
        String extName = url.getParameter("type", url.getParameter("model", "BMW"));	
        if (extName == null)	
            throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.test.Car) name from url (" + url.toString() + ") use keys([type, model])");	
        // 最后还是调用上一篇介绍的getExtension()方法	
        org.apache.dubbo.common.extension.test.Car extension =	
           (org.apache.dubbo.common.extension.test.Car) ExtensionLoader	
           .getExtensionLoader(org.apache.dubbo.common.extension.test.Car.class)	
           .getExtension(extName); 	
        extension.start(arg0, arg1);	
    }	
	
    public void stop(org.apache.dubbo.common.URL arg0, java.lang.String arg1) {	
       ... // 生成的stop()方法与start()方法类似,不再粘贴	
    }	
}

最后,看一下 AdaptiveClassCodeGenerator.generate() 方法是如何生成 Adaptive代理类的:

 
   
public String generate() {	
    StringBuilder code = new StringBuilder(); 	
    code.append(generatePackageInfo()); // 生成package部分	
    code.append(generateImports()); // 生成import部分	
    code.append(generateClassDeclaration()); // 生成类签名 	
    Method[] methods = type.getMethods(); // 根据SPI接口生成相应的方法代码	
    for (Method method : methods) {	
        code.append(generateMethod(method));	
    }	
    code.append("}");	
    return code.toString();	
}

@Activate注解的事情,下次再说?

640?wx_fmt=png


你可能感兴趣的:(Dubbo 2.7.3源码分析——Dubbo SPI(二))