逐步深入 Dubbo SPI 原理

什么是 SPI ?

SPI(Service Provider Interface) 是一种服务发现机制, 主要原理是在运行时根据具体参数去查找约定路径(JDK 默认是在/META-INF/services/)下的配置实现类信息。 通过然后类加载机制(ClassLoader)实现对类信息的加载以及后面的实例化,实现黑盒扩展的作用。简单点来说就是在运行时动态指定并加载实现类,实现指定功能点扩展。

推荐连接高级开发必须理解的Java中SPI机制

Dubbo SPI 和JDK SPI 的区别

是的,Dubbo并没有沿用JDK内置的SPI机制。 而是自行实现了一套SPI机制,从Dubbo的官方描述来看,Dubbo是对JDK的SPI进行了一次改进。 那这里就有必要说明下。JDK SPI有哪些问题呢? Dubbo 又是如何对其进行改进的呢?

JDK SPI问题

  • 需要遍历所有的实现,并实例化,然后我们在循环中才能找到我们需要的实现。
  • 配置文件中只是简单的列出了所有的扩展实现,而没有给他们命名。导致在程序中很难去准确的引用它们。
  • 扩展如果依赖其他的扩展,做不到自动注入和装配
  • 不提供类似于Spring的IOC和AOP功能
  • 扩展很难和其他的框架集成,比如扩展里面依赖了一个Spring bean,原生的Java SPI不支持

Dubbo SPI的增强

  • 通过Map缓存的机制, 对Class类进行缓存,在运行时调用明确名称的类。 获取对应的类信息,进行实例化。 提高了启动性能
  • Dubbo SPI 实现了类似IOC 和AOP 的机制。提供了查找和动态扩展的功能。
  • Dubbo 也提供了从扩展点容器获取实例对象的功能, 如通过SpringExtensionFactory就实现了通过Spring的容器获取依赖的功能。 可以更好的和其他框架进行集成。

Dubbo SPI示例

代码示例

@SPI
public interface Car {
    void test();
}

//宝马
public class BWMCar implements Car {

    @Override
    public void test() {
        System.out.println("hi, 我是宝马车");
    }
}

public static void main(String[] args) {
        ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);

        Car car = extensionLoader.getExtension("bwm");
        car.test();
}

配置示例
在/META-INF/dubbo/目录下创建文件com.wgt.dubbo.samples.spi.demo1.car.Car文件,配置信息如下

bwm=com.wgt.dubbo.samples.spi.demo1.car.BWMCar

输出结果

hi, 我是宝马车

从示例中的来看。 流程时先ExtensionLoader.getExtensionLoader(Class class) 获取到一个ExtensionLoader示例, 然后调用ExtensionLoader的getExtension(String name)方法获取到目标实例, 那么我们从ExtensionLoader.getExtensionLoader(Car.class)开始,进入源码,弄清原理。

代码的示例很简单, 但是里面的学问有很多, 不过不需要心急,我们需要确保看源码的过程中不会迷失。 接下来的源码解析将围绕着本段示例代码来进行步步深入分析, 尽量做到细微入致。

Dubbo 加载拓展点

  • 创建ExtensionLoader实例源码
@SuppressWarnings("unchecked")
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
    if (type == null) {
        throw new IllegalArgumentException("Extension type == null");
    }
    // 判断type 是否为接口
    if (!type.isInterface()) {
        throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!");
    }
    // 判断是否标注了@SPI注解
    if (!withExtensionAnnotation(type)) {
        throw new IllegalArgumentException("Extension type (" + type +
                ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!");
    }
        // 从缓存中获取ExtensionLoader。 
    ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    if (loader == null) {
        //缓存中没有的话 则创建一个新的ExtensionLoader加入缓存中。
        EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type));
        loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
    }
    return loader;
}

//构造方法
private ExtensionLoader(Class<?> type) {
        this.type = type;
            //此处默认的objectFactory 为org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory,
            // 该处的objectFactory 相当于一个IOC容器。 后面在IOC部分详细说明
        objectFactory = (type == ExtensionFactory.class ? null :            ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

从中可以看出创建ExtensionLoader实例的源码是比较简单的。 疑问点objectFactory对象工厂我们放到后面再说。接下来我们先看getExtension方法都做了什么。

  • getExtension方法源码分析
@SuppressWarnings("unchecked")
public T getExtension(String name) {
    if (StringUtils.isEmpty(name)) {
        throw new IllegalArgumentException("Extension name == null");
    }
    if ("true".equals(name)) {
        return getDefaultExtension();
    }
    // 缓存中根据name 获取加载过的实例
    final Holder<Object> holder = getOrCreateHolder(name);
    Object instance = holder.get();
   
    // double check synchronized 确保线程安全
    if (instance == null) {
        synchronized (holder) {
            instance = holder.get();
            if (instance == null) {
                // 创建新的Extension 信息
                instance = createExtension(name);
                holder.set(instance);
            }
        }
    }
    return (T) instance;
}

// 创建新的实例对象
@SuppressWarnings("unchecked")
private T createExtension(String name) {
  // 此处会读取/META-INF/services/ 下的扩展点文件信息。 将所有类信息加载进来后根据name 获取具体的实例对象
  // 当前示例主要说明点。 
  Class<?> clazz = getExtensionClasses().get(name);
  if (clazz == null) {
    throw findException(name);
  }
  try {
    T instance = (T) EXTENSION_INSTANCES.get(clazz);
    if (instance == null) {
      // 根据Class 信息通过反射进行实例化。 
      EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
      instance = (T) EXTENSION_INSTANCES.get(clazz);
    }
    
    // 进行依赖注入 在IOC 内容详细说明
    injectExtension(instance);
    
    // AOP 内容。 使用装饰器模式实现 在AOP部分详细说明
    Set<Class<?>> wrapperClasses = cachedWrapperClasses;
    if (CollectionUtils.isNotEmpty(wrapperClasses)) {
      for (Class<?> wrapperClass : wrapperClasses) {
        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
      }
    }
    // 调用instance 的初始化方法。 如果class 实现了Lifecycle 接口的话
    initExtension(instance);
    return instance;
  } catch (Throwable t) {
    throw new IllegalStateException("Extension instance (name: " + name + ", class: " +
                                    type + ") couldn't be instantiated: " + t.getMessage(), t);
  }
}

此处我们可以看出获取扩展点实例的大致流程如下:

加载接口类型的所有实现类Class信息并缓存 ----> 根据name去获取指定扩展实现类Class ----> 通过反射机制对实现类Class进行实例化并缓存 ----> 基于dubbo 的IOC机制对实例对象进行依赖注入----> 判断是否有包装类(wrapperClasses),决定是否要生成代理对象 ----> 最后对对象进行初始化之后返回该实例对象

  • 第一步实现类的加载 getExtensionClasses()
private Map<String, Class<?>> getExtensionClasses() {
    Map<String, Class<?>> classes = cachedClasses.get();
    if (classes == null) {
        synchronized (cachedClasses) {
            classes = cachedClasses.get();
            if (classes == null) {
                // 加载所有class
                classes = loadExtensionClasses();
                cachedClasses.set(classes);
            }
        }
    }
    return classes;
}

private Map<String, Class<?>> loadExtensionClasses() {
            // 通过@SPI("DefaultExtensionName") 缓存默认的extension name
        cacheDefaultExtensionName();
  
                //根据type.name  查找各个配置文件路径下的实现类拓展配置
        Map<String, Class<?>> extensionClasses = new HashMap<>();
        // internal extension load from ExtensionLoader's ClassLoader first
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName(), true);
        loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"), true);

        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
        loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        return extensionClasses;
}

private void loadDirectory(Map<String, Class<?>> extensionClasses, 
                           String dir, 
                           String type, 
                           boolean extensionLoaderClassLoaderFirst) {
        String fileName = dir + type;
        try {
            Enumeration<java.net.URL> urls = null;
          
            // 获取ClassLoader
            ClassLoader classLoader = findClassLoader();
            
            // try to load from ExtensionLoader's ClassLoader first
            // 如果需要先尝试使用ExtensionLoader 查找文件资源
            if (extensionLoaderClassLoaderFirst) {
                ClassLoader extensionLoaderClassLoader = ExtensionLoader.class.getClassLoader();
                if (ClassLoader.getSystemClassLoader() != extensionLoaderClassLoader) {
                    urls = extensionLoaderClassLoader.getResources(fileName);
                }
            }
          
            // 使用默认加载器获取资源文件
            if(urls == null || !urls.hasMoreElements()) {
                if (classLoader != null) {
                    urls = classLoader.getResources(fileName);
                } else {
                    urls = ClassLoader.getSystemResources(fileName);
                }
            }
                        
            if (urls != null) {
                while (urls.hasMoreElements()) {
                    java.net.URL resourceURL = urls.nextElement();
                    // 读取类配置, 加载类信息
                    loadResource(extensionClasses, classLoader, resourceURL);
                }
            }
        } catch (Throwable t) {
            logger.error("Exception occurred when loading extension class (interface: " +
                    type + ", description file: " + fileName + ").", t);
        }
}

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
  
  //.... 此处省略文件解析部分代码,不在主逻辑范畴,有兴趣的同学可以自行深入

  // 遍历加载class实现类, 通过Class.forName(line, true, classLoader) 实现类加载
  loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
  //.... 此处省略异常处理
}

//解析类信息
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
        if (!type.isAssignableFrom(clazz)) {
            throw new IllegalStateException("Error occurred when loading extension class (interface: " +
                    type + ", class line: " + clazz.getName() + "), class "
                    + clazz.getName() + " is not subtype of interface.");
        }
            // 缓存标注了@Adaptive的扩展实现类, 具体作用后面自适应问题详细说明
        if (clazz.isAnnotationPresent(Adaptive.class)) {
            cacheAdaptiveClass(clazz);
        // 判断是否为包装类, 缓存包装类
        } else if (isWrapperClass(clazz)) {
            cacheWrapperClass(clazz);
        } else {
            clazz.getConstructor();
            // 如果没有指定name 生成name
            if (StringUtils.isEmpty(name)) {
                name = findAnnotationName(clazz);
                if (name.length() == 0) {
                    throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
                }
            }
                        // 分割name, 因为可以多个name 指向同一个实现类。例: aa, bb = org.apache.dubbo.extension.C
            String[] names = NAME_SEPARATOR.split(name);
            if (ArrayUtils.isNotEmpty(names)) {
                // 缓存@Activate标注的类信息
                cacheActivateClass(clazz, names[0]);
                for (String n : names) {
                    // 遍历缓存 name和class 信息
                    cacheName(clazz, n);
                    saveInExtensionClass(extensionClasses, clazz, n);
                }
            }
        }
}

到此基本了解了拓展类的加载过程, 基本逻辑也非常简单。 获取到classLoader并调用Class.forname("className")将扩展类信息加载进JVM中, 之后就是从功能扩展的维度,对class进行不同纬度的缓存, 方便后面在某项功能点中快速获取类信息。

Dubbo IOC实现

  • 基于IOC的依赖注入 injectExtension(instance)
private T injectExtension(T instance) {

    if (objectFactory == null) {
        return instance;
    }

    try {
        for (Method method : instance.getClass().getMethods()) {
            if (!isSetter(method)) {
                continue;
            }
            /**
             * Check {@link DisableInject} to see if we need auto injection for this property
             */
            if (method.getAnnotation(DisableInject.class) != null) {
                continue;
            }
            Class<?> pt = method.getParameterTypes()[0];
            if (ReflectUtils.isPrimitives(pt)) {
                continue;
            }

            try {
                String property = getSetterProperty(method);
                Object object = objectFactory.getExtension(pt, property);
                if (object != null) {
                    method.invoke(instance, object);
                }
            } catch (Exception e) {
                logger.error("Failed to inject via method " + method.getName()
                        + " of interface " + type.getName() + ": " + e.getMessage(), e);
            }

        }
    } catch (Exception e) {
        logger.error(e.getMessage(), e);
    }
    return instance;
}

从上面的逻辑很简单的看出大致逻辑就是先获取所有的setter方法,获取setter方法的参数的类型和名称, 最后通过我们前面看到过的objectFactory获取到依赖对象, 然后对其进行依赖注入。 那这里我们就需要深扒一下这个objectFactory 到底是个神马。前面我在objectfactory的注释说明了默认为AdaptiveExtensionFactory类, 我们先来看下他的源码

@Adaptive
public class AdaptiveExtensionFactory implements ExtensionFactory {
        // 保存了ExtensionFactory的实现类。 
    private final List<ExtensionFactory> factories;
        
    public AdaptiveExtensionFactory() {
        // 初始化将 其他ExtensionFactory拓展类加载缓存
        ExtensionLoader<ExtensionFactory> loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class);
        List<ExtensionFactory> list = new ArrayList<ExtensionFactory>();
        for (String name : loader.getSupportedExtensions()) {
            list.add(loader.getExtension(name));
        }
        factories = Collections.unmodifiableList(list);
    }

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        for (ExtensionFactory factory : factories) {
            T extension = factory.getExtension(type, name);
            if (extension != null) {
                return extension;
            }
        }
        return null;
    }
}

很明显该类是一个策略类, 启动时候会讲其他的ExtensionFactory实现类加载进来, 然后再获取Extension实现的时候直接遍历各个容器来进行查找。那我们来看下ExtensionFactory默认实现的子类。

// SPI 默认的IOC实现,
public class SpiExtensionFactory implements ExtensionFactory {

    @Override
    public <T> T getExtension(Class<T> type, String name) {
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
            if (!loader.getSupportedExtensions().isEmpty()) {
                return loader.getAdaptiveExtension();
            }
        }
        return null;
    }
}

// Spring 实现
public class SpringExtensionFactory implements ExtensionFactory {
    private static final Logger logger = LoggerFactory.getLogger(SpringExtensionFactory.class);

    private static final Set<ApplicationContext> CONTEXTS = new ConcurrentHashSet<ApplicationContext>();

    public static void addApplicationContext(ApplicationContext context) {
        CONTEXTS.add(context);
        if (context instanceof ConfigurableApplicationContext) {
            ((ConfigurableApplicationContext) context).registerShutdownHook();
        }
    }

    public static void removeApplicationContext(ApplicationContext context) {
        CONTEXTS.remove(context);
    }

    public static Set<ApplicationContext> getContexts() {
        return CONTEXTS;
    }

    // currently for test purpose
    public static void clearContexts() {
        CONTEXTS.clear();
    }

    @Override
    @SuppressWarnings("unchecked")
    public <T> T getExtension(Class<T> type, String name) {

        //SPI should be get from SpiExtensionFactory
        if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
            return null;
        }

        for (ApplicationContext context : CONTEXTS) {
            T bean = BeanFactoryUtils.getOptionalBean(context, name, type);
            if (bean != null) {
                return bean;
            }
        }

        logger.warn("No spring extension (bean) named:" + name + ", try to find an extension (bean) of type " + type.getName());

        return null;
    }
}

以上ExtensionFactory主要分为两类

  • SpiExtensionFactory是Dubbo 的IOC默认容器,对象是从前面加载缓存中获取的SPI的扩展对象。 这是Dubbo 的默认机制

  • SpringExtensionFactory是使用Spring的IOC容器。我们可以将Spring容器中的Bean注入到Extension实例中,通过该容器我们可以更好的继承Spring框架甚至其他组件。

Dubbo Aop实现

可能用过一些容器框架的同学都知道AOP的基本原理。 甚至有的同学可能首先想到的就是动态代理模式,这并没有错。 但是方法不是唯一的, Dubbo中获取的代理对象是通过装饰器模式实现的,接下来我们继续深入看下源码。

通过前面创建Extension我们可以知道, 是否需要生成对实力对象进行封装的依据是cachedWrapperClasses是否为空。同时通过类信息加载过程可以知道,在加载类的时候会对cachedWrapperClasses进行缓存, 那我们这边举例一个wrapper类,然后再次运行。

  • CarWrapper
public class CarWrapper implements Car{

    private Car car;

    public CarWrapper(Car car){
        this.car = car;
    }

    @Override
    public void test(CarBrand brand) {
        System.out.println("包装类前置执行");
        car.test(brand);
        System.out.println("包装类后置执行");
    }
}
  • 配置信息

bwm=com.wgt.dubbo.samples.spi.demo1.car.BWMCar
audi=com.wgt.dubbo.samples.spi.demo1.car.AudiCar

# 代理类配置
com.wgt.dubbo.samples.spi.demo1.car.CarWrapper
  • 控制台输出
包装类前置执行
hi, 我是宝马车
包装类后置执行

从输出结果来看, 很明显在我根据(name="bwm"), 去获取Extension实例的时候,实际获取到的是CarWrapper类, 只不过BWMCar是作为一个代理对象传入到CarWrapper中。

  • 源码参考
Set<Class<?>> wrapperClasses = cachedWrapperClasses;
if (CollectionUtils.isNotEmpty(wrapperClasses)) {
    for (Class<?> wrapperClass : wrapperClasses) {
        // 获取CarWrapper类的有参构造起, 并将instance作为构造参数返回新的包装类。
        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
    }
}

当前方式可以获取到包装后的实例对象, 但是此种方法还是不够灵活。 Dubbo 中还有一种自适应的机制, 可以根据请求参数动态的获取拓展类进行调用, 通过策略+装饰器的模式实现更加完善的代理

Dubbo 自适应机制

第一次听到自适应机制难免有些陌生, 扩展的自适应实例其实就是一个Extension的代理,它实现了扩展点接口。在调用扩展点的接口方法时,会根据实际的参数来决定要使用哪个扩展。

  • @Adaptive

    @Adaptive注解是作用在做自适应扩展点的注解类, 可以作用在类上和方法上

    • 当@Adaptive标注在类上时,在调用getAdaptiveExtension方法时,直接返回该类,表示代理类由手工实现,并不需要Dubbo自动生成实现类。 可以参考 AdaptiveCompiler 和 AdaptiveExtensionFactory 实现
    • 当@Adaptive标注在接口的方法上时, 表明调用该方法可以通过URL参数动态调用扩展实现类的对应方法。
    • 被@Adaptive修饰得方法得参数 必须满足参数中有一个是URL类型,或者有至少一个参数有一个公共的返回URL的get方法
    • 在调用动态生成代理类的非@Adaptive方法是, 默认会抛出UnsupportedOperationException异常
  • 代码示例

    说明: @Adaptive标注在类上的逻辑相当简单。 这里就不赘述了。 直接以标注在方法上的例子为例

    @SPI
    public interface Car {
          
          //动态生成代理类接口
        @Adaptive
        void test(CarBrand brand);
    }
    
    //自适应节点, 获取org.apache.dubbo.common.URL参数的接口
    public interface CustomerAdaptiveNode {
        URL getUrl();
    }
    
    //车品牌
    public interface CarBrand extends CustomerAdaptiveNode {
    
        CarBrand BWM = getCarBrand("bwm");
        CarBrand AUDI = getCarBrand("audi");
        CarBrand BENZ = getCarBrand("benz");
    
        static CarBrand getCarBrand(String brandName){
            return new CarBrand() {
                @Override
                public URL getUrl() {
                    URL url = new URL(null, null, 0);
                    url = url.addParameter("car", brandName);
                    return url;
                }
            };
        }
    }
    
    //宝马车
    public class BWMCar implements Car {
    
        @Override
        public void test(CarBrand brand) {
            System.out.println("hi, 我是宝马车");
        }
    }
    //..... 此处省略 奔驰车和奥迪车源码
    
    
    //Main方法
    public static void main(String[] args) {
            ExtensionLoader<Car> extensionLoader = ExtensionLoader.getExtensionLoader(Car.class);
    
            //Car car = extensionLoader.getExtension("bwm");
                  // 获取自适应代理实例
            Car car = extensionLoader.getAdaptiveExtension();
                  //通过车品牌调用接口
            car.test(CarBrand.BENZ);
            car.test(CarBrand.BWM);
            car.test(CarBrand.AUDI);
    }
    
  • 控制台输出
hi, 我是奔驰车
hi,我是宝马车
hi, 我是奥迪车

可以看到,我们在调用同一个实例的同一个方法, 我们根据不同的CarBrand 参数实现了自适应的去调用对应扩展实例的方法。

  • 源码参考

    public T getAdaptiveExtension() {
          // 一如既往的缓存套路
        Object instance = cachedAdaptiveInstance.get();
        if (instance == null) {
            if (createAdaptiveInstanceError != null) {
                throw new IllegalStateException("Failed to create adaptive instance: " +
                        createAdaptiveInstanceError.toString(),
                        createAdaptiveInstanceError);
            }
                  
            synchronized (cachedAdaptiveInstance) {
                instance = cachedAdaptiveInstance.get();
                if (instance == null) {
                    try {
                          // 创建自适应实例
                        instance = createAdaptiveExtension();
                        cachedAdaptiveInstance.set(instance);
                    } catch (Throwable t) {
                        createAdaptiveInstanceError = t;
                        throw new IllegalStateException("Failed to create adaptive instance: " + t.toString(), t);
                    }
                }
            }
        }
        return (T) instance;
    }
    
    private T createAdaptiveExtension() {
            try {
                  // 获取自适应扩展类Class, 调用newInstance方法进行实例化,然后进行注入
                return injectExtension((T) getAdaptiveExtensionClass().newInstance());
            } catch (Exception e) {
                throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
            }
    }
    
    private Class<?> getAdaptiveExtensionClass() {
                  // 眼熟的初始加载加载扩展累信息
            getExtensionClasses();
                  // 判断是否有标注在类上的@Adaptive 扩展类
            if (cachedAdaptiveClass != null) {
                return cachedAdaptiveClass;
            }
                  //动态生成自适应扩展累
            return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
    
    private Class<?> createAdaptiveExtensionClass() {
                  // 自适应扩展累代码生成器 生成源代码,
                  // 此处不在深入生成代码原理, 有兴趣的小伙伴自行研究, 下面会贴出生成好的代码
            String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
                  //获取classLoader
            ClassLoader classLoader = findClassLoader();
                  // 获取编译器
            org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
                  // 将源代码进行编译生成Class 对象
            return compiler.compile(code, classLoader);
    }
    
  • 自适应扩展累源码

    package com.wgt.dubbo.samples.spi.demo1.car;
    
    import org.apache.dubbo.common.extension.ExtensionLoader;
    
    public class Car$Adaptive implements com.wgt.dubbo.samples.spi.demo1.car.Car {
        public void test(com.wgt.dubbo.samples.spi.demo1.brand.CarBrand arg0) {
          if (arg0 == null)
              throw new IllegalArgumentException("com.wgt.dubbo.samples.spi.demo1.brand.CarBrand argument == null");
            
          if (arg0.getUrl() == null)
              throw new IllegalArgumentException("com.wgt.dubbo.samples.spi.demo1.brand.CarBrand argument getUrl() == null");
            
          org.apache.dubbo.common.URL url = arg0.getUrl();
          String extName = url.getParameter("car", "bwm");
          if (extName == null)
              throw new IllegalStateException("Failed to get extension (com.wgt.dubbo.samples.spi.demo1.car.Car) name from url (" + url.toString() + ") use keys([car])");
          
          com.wgt.dubbo.samples.spi.demo1.car.Car extension = (com.wgt.dubbo.samples.spi.demo1.car.Car)       ExtensionLoader.getExtensionLoader(com.wgt.dubbo.samples.spi.demo1.car.Car.class).getExtension(extName);
          extension.test(arg0);
        }
    }
    

    从以上的源代码可以看出, 所谓的根据参数自适应,其实就是通过URL对象中的parameter 参数去获取对应的扩展实现。 获取的扩展的方式同样是通过策略模式进行实现。 不过此种方式可以确保在运行时才能确定具体执行的扩展累,对于SPI机制的灵活性来说非常的有意义的。

Dubbo @Activate

​ 相较于前面的内容,都是根据一些特定参数获取具体的扩展类, 但其实除此之外, 我们还会有同时用到多个扩展类,最常见的就是Dubbo 的Filter 机制,当我们需要在执行rpc调用的前后做一个操作时, 我们就可以通过实现Filter接口,并将其配置到org.apache.dubbo.rpc.Filter下。 但是我们在一次rpc调用时可能会有多个filter, 那么如何决定每个filter 的作用域,以及执行顺序呢?

此时@Activate的作用的出现了,我们可以通过group, 和value 两者来决定是否此次的调用启用对应拦截器,并通过order 排序决定执行顺序。

  • 源码参考
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Activate {
    /**
     *  根据分组 
     *  例如 
     *      CommonConstants.PROVIDER
     *      CommonConstants.CONSUMER
     * @return
     */
    String[] group() default {};
        
    /**
     *  根据org.apache.dubbo.common.URL 中的parameters 参数决定是否启用
     * @return
     */
    String[] value() default {};

    @Deprecated
    String[] before() default {};

    @Deprecated
    String[] after() default {};
    
    /**
     * Filter 顺序
     * @return
     */
    int order() default 0;
}
  • 拦截器获取源码逻辑
  public List<T> getActivateExtension(URL url, String[] values, String group) {
          List<T> activateExtensions = new ArrayList<>();
          List<String> names = values == null ? new ArrayList<>(0) : asList(values);
          if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
              getExtensionClasses();
              for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
                  String name = entry.getKey();
                  Object activate = entry.getValue();
  
                  String[] activateGroup, activateValue;
  
                  if (activate instanceof Activate) {
                      activateGroup = ((Activate) activate).group();
                      activateValue = ((Activate) activate).value();
                  } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
                      activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
                      activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
                  } else {
                      continue;
                  }
                  if (isMatchGroup(group, activateGroup)
                          && !names.contains(name)
                          && !names.contains(REMOVE_VALUE_PREFIX + name)
                          && isActive(activateValue, url)) {
                      activateExtensions.add(getExtension(name));
                  }
              }
              activateExtensions.sort(ActivateComparator.COMPARATOR);
          }
          List<T> loadedExtensions = new ArrayList<>();
          for (int i = 0; i < names.size(); i++) {
              String name = names.get(i);
              if (!name.startsWith(REMOVE_VALUE_PREFIX)
                      && !names.contains(REMOVE_VALUE_PREFIX + name)) {
                  if (DEFAULT_KEY.equals(name)) {
                      if (!loadedExtensions.isEmpty()) {
                          activateExtensions.addAll(0, loadedExtensions);
                          loadedExtensions.clear();
                      }
                  } else {
                      loadedExtensions.add(getExtension(name));
                  }
              }
          }
          if (!loadedExtensions.isEmpty()) {
              activateExtensions.addAll(loadedExtensions);
          }
          return activateExtensions;
      }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,825评论 4 377
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,887评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,425评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,801评论 0 224
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,252评论 3 299
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,089评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,216评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 31,005评论 0 215
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,747评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,883评论 2 255
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,354评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,694评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,406评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,222评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,242评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,017评论 2 281