天天看点

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

Dubbo

中可以通过

SPI

机制来加载对应扩展点实现,可是一个扩展点对应着多个实现类,我们如何才能触发目标实现类中的方法呢?

Dubbo

Adaptive

机制就是解决这个问题的。
下面我们用代码来看一下@Adaptive注解的使用方式以及源码实现。
  1. 扩展点接口

    ICarService

package com.dubbo.service;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;

@SPI
public interface ICarService {
    void speed(URL url);
}
           
  1. 两个扩展点实现类

    AudiService

    BMWService

package com.dubbo.service.impl;
import com.dubbo.service.ICarService;
import org.apache.dubbo.common.URL;

// 实现类 奥迪
public class AudiService implements ICarService {
    @Override
    public void speed(URL url) {
        System.out.println("audi drive speed 100KM/H");
    }
}

// 实现类 宝马
public class BMWService implements ICarService {
    @Override
    public void speed(URL url) {
        System.out.println("bmw drive speed 200KM/H");
    }
}
           
  1. 扩展点配置文件

    META-INF/services/com.lbb.service.ICarService

audi=com.lbb.service.impl.AudiService
bmw=com.lbb.service.impl.BMWService
           

一、类上添加

@Adaptive

注解

我们可以在类上添加

@Adaptive

注解来标示该类为当前扩展点的代理类,在代理类的方法中会根据传入参数从而自行选择实现类来进行调用。下面我们用具体的代码来实现一个代理类:

  1. 首先创建一个Adaptive类,集成扩展点接口
package com.lbb.adaptive;

import com.lbb.service.ICarService;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.ExtensionLoader;

@Adaptive
public class CarServiceAdaptive implements ICarService {

    @Override
    public void speed(URL url) {
        // 从url参数中获取carType 
        String carType = url.getParameter("carType");
        // 根据carType去获取对应的扩展点实现类
        ICarService carService = ExtensionLoader.getExtensionLoader(ICarService.class).getExtension(carType);
        // 调用扩展点实现类的实例的speed方法
        carService.speed(url);
    }
}
           
  1. 并将其添加到扩展点文件中
audi=com.lbb.service.impl.AudiService
bmw=com.lbb.service.impl.BMWService

adaptive=com.lbb.adaptive.CarServiceAdaptive  # 此处添加的是适配代理类
           
  1. 下面是执行方法
public static void main(String[] args) {
    // 获取自适应方法
    ICarService carService = ExtensionLoader.getExtensionLoader(ICarService.class).getAdaptiveExtension();

    Map<String, String> map = new HashMap<>();
    map.put("carType", "bmw");
    URL url = new URL("", "",  0, map);
    carService.speed(url);
}

// 输出结果
bmw drive speed 200KM/H
           

二、方法上添加

@Adaptive

注解

我们也可以在扩展点接口的方法上添加

@Adaptive

注解,从而让Dubbo为我们自动生成代理类,在代理类中也是会获取url参数,从而自行选择实现类来进行调用。下面我们用具体的代码来实现一下:

  1. 我们在

    ICarService

    类的

    speed()

    方法上添加

    @Adaptive

    注解,并在其参数中设置url参数对应的key
package com.lbb.service;

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

@SPI
public interface ICarService {

    @Adaptive("carType")
    void speed(URL url);

}
           
  1. 执行方法同上,打印内容如下所示:
bmw drive speed 200KM/H
           

三、源码解析

为什么在方法上添加@Adaptive注解就能够自动为我们找到目标实现类呢?

从我们自己实现代理类中可知,Dubbo应该后台为我们生成了一个代理类。在这个代理类中会解析url中携带的参数,从而获取到目标实现类的别名,从而通过SPI机制获取对应的实例对象,再调用实例对象的方法。

  1. 这里我们便从

    main

    方法中的

    getAdaptiveExtenion()

    方法为入口,探究Dubbo是如何实现的
// 每个接口都对应一个扩展加载器
ICarService carService = ExtensionLoader.getExtensionLoader(ICarService.class).getAdaptiveExtension();
           
  1. 这里会尝试着先从缓存中获取代理类对象实例,如果没有获取到再触发创建

    org.apache.dubbo.common.extension.ExtensionLoader#getAdaptiveExtension

public T getAdaptiveExtension() {
    // 从缓存中获取扩展点对应的代理类对象实例
    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("fail to create adaptive instance: " + t.toString(), t);
                    }
                }
            }
        } else {
            throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
        }
    }

    return (T) instance;
}
           
  1. 创建代理类、进行实例化、依赖注入

    org.apache.dubbo.common.extension.ExtensionLoader#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);
    }
}
           
  1. 这里会去检测是否存在类上配置了

    @Adaptive

    注解的代理类,如果存在则直接返回该代理类,如果不存在则创建一个代理类

    org.apache.dubbo.common.extension.ExtensionLoader#getAdaptiveExtensionClass

private Class<?> getAdaptiveExtensionClass() {
    // 加载扩展点实现类
    getExtensionClasses();
    // 如果存在在类上配置@Adaptive的代理类,则直接返回该代理类
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    // 如果不存在,则创建一个代理类(下面会详说)
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}
           
  1. 生成代理类的代码,并进行编译,我们等会将这个代理类的代码打印出来看看,看看到底有什么玄机在里面
private Class<?> createAdaptiveExtensionClass() {
    // 生成代理类的字符串
    String code = createAdaptiveExtensionClassCode();

    // 这里我们打印出来看看这个代理类的代码
    System.out.println(code);
    
    // 下面是对这个代理类字符串进行编译,生成代理类Class对象
    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);
}
           
  1. 打印出的代理类代码
package com.lbb.service;
import org.apache.dubbo.common.extension.ExtensionLoader;
public class ICarService$Adaptive implements com.lbb.service.ICarService {
    public void speed(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("carType");
    if(extName == null) throw new IllegalStateException("Fail to get extension(com.lbb.service.ICarService) name from url(" + url.toString() + ") use keys([carType])");
    com.lbb.service.ICarService extension = (com.lbb.service.ICarService)ExtensionLoader.getExtensionLoader(com.lbb.service.ICarService.class).getExtension(extName);
    extension.speed(arg0);
    }
}
           

看到这里相信大家已经对Dubbo的自适应扩展机制已经有了一定的了解,大家可以根据我的思路去调试下,谢谢大家的阅读。