Dubbo 中的 SPI 机制

277 阅读6分钟

概述

本文主要讲述 Dubbo 中的 SPI 机制, 首先给大家带来一个简单的 Spring-Boot-Dubbo 案例,然后通过 Java 中的 SPI 的机制和讲解来引出 SPI 解决的问题,最后再通过一下几个方面讲述 Dubbo 中的 SPI 的设。

  1. Dubbo 中的 SPI 实现
  2. Dubbo 中的依赖注入
  3. Dubbo 中的 AOP
  4. Dubbo 中的 Adaptive 机制

版本说明:

dubbo 2.7.8

Spring-Boot-Dubbo 简单案例

Spring Boot 整合 Dubbo 版本说明:

spring-boot 2.3.0.RELEASE

dubbo 2.7.8

demo 源码地址

Dubbo 更多功能的 资料地址

什么是 SPI 机制 ?

SPI 机制

在双亲委托模型下,类加载是由下而上的,即下层的类加载器会委托上层进行加载。Java 为了实现可拓展性在核心库中只定义了接口信息,而这些接口的实现可以由不同的三方厂家实现。但是 Java 的启动类加载器不会加载其它来源的 jar 包,这样就无法满足 Java 的拓展性需求。

针对这一个问题 Java 提供了线程上下文类加载器来解决这个问题, 他可以让父 ClassLoader 可以使用当前线程Thread.currentThread().getContextLoader(). 所指定的 ClassLoader 加载的类。这就改变了父 ClassLoader 不能使用 ClassLoader 或是没有直接父子关系的 ClassLoader 加载的类的情况,即改变了双亲委托模型。

这个整个打破双亲委派模型的类加载机制,就是 Java 的 SPI(Service Provider Interface) 机制。它有很多我们熟知的案例 JDBCSpring-BootDubbo

ServiceLoader 核心原理

这个整个过程

Java 中 SPI 的使用

  1. 定义接口和实现类
// 接口类
public interface Car {
    void start();
}

// 实现类
public class RedCar implements Car {
    private String color = "red";
      
    @Override
    public void start() {
        System.out.println(this.color + " car start!");
    }
}  
  1. 定义 META-INF/services/ cn.spi.Car 文件文件内容
cn.spi.RedCar
  1. 通过 SPI 获取实例对象
ServiceLoader<Car> cars = ServiceLoader.load(Car.class);
for (Car car : cars) {
  car.start();
}
  1. 获取代理类

Dubbo 中的 SPI

在 Dubbo 中 SPI 的流程是这样的首先 ExtensionLoaderDubbo SPI 实现的核心类,它下面有连个核心方法 getExtensioncreateExtension 。在 createExtension 中完成对象的创建,在这个创建的过程中一共有以下几个步骤:

  1. 读取配置文件通过 name 参数找到实现类;
  2. 对实现类进行实例化;
  3. 依赖注入;
  4. AOP;
  5. 返回对象实例。

一. Dubbo 中的 SPI 实现

使用步骤

还是使用我们之前 Car 接口,下面是咱们有变化的部分

  1. 定义配置文件 META-INF/dubbo/ cn.spi.Car 文件内容
red=cn.spi.RedCar
  1. 获取对象实例
ExtensionLoader<Car> extensionLoader = ExtensionLoader
  .getExtensionLoader(Car.class);
Car car = extensionLoader.getExtension("red");
  1. 依赖注入实现代理类
//  定义
@SPI
public interface Car {

    void start();

    /**
     * 方法被代理需要两个步骤:
     * 1. 增加 @Adaptive 注解
     * 2. URL 参数,
     *
     * @param url 服务提供者的 URL
     * @return
     */
    @Adaptive
    String color(URL url);
}

// 使用
ExtensionLoader<Person> extensionLoader2 = ExtensionLoader.getExtensionLoader(Person.class);
Person person = extensionLoader2.getExtension("black");
URL url = new URL("http", "localhost", 8080); //代理逻辑
url = url.addParameter("car", "red");
person.getCar().color(url);

源码分析

getExtension 方法源码分析

// 获取实例
public T getExtension(String name, boolean wrap) {
  if (StringUtils.isEmpty(name)) {
    throw new IllegalArgumentException("Extension name == null");
  }
  if ("true".equals(name)) {
    return getDefaultExtension();
  }
  final Holder<Object> holder = getOrCreateHolder(name);
  Object instance = holder.get();
  if (instance == null) { // DCL
    synchronized (holder) {
      instance = holder.get();
      if (instance == null) {
        // 创建对象
        instance = createExtension(name, wrap);
        holder.set(instance);
      }
    }
  }
  return (T) instance;
}

// 创建实例
private T createExtension(String name, boolean wrap) {
  Class<?> clazz = getExtensionClasses().get(name);
  if (clazz == null) {
    throw findException(name);
  }

  T instance = (T) EXTENSION_INSTANCES.get(clazz);
  if (instance == null) {
    EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
    instance = (T) EXTENSION_INSTANCES.get(clazz);
  }
  // ....  ioc, aop
  return instance;
}

// 读取类
private Map<String, Class<?>> getExtensionClasses() {
  Map<String, Class<?>> classes = cachedClasses.get();
  if (classes == null) {
    synchronized (cachedClasses) {
      classes = cachedClasses.get();
      if (classes == null) {
        classes = loadExtensionClasses();
        cachedClasses.set(classes);
      }
    }
  }
  return classes;
}

// 加载类
private Map<String, Class<?>> loadExtensionClasses() {
  cacheDefaultExtensionName();

  Map<String, Class<?>> extensionClasses = new HashMap<>();

  // LoadingStrategy 策略接口,读取 SPI 目录下的类
  for (LoadingStrategy strategy : strategies) {
    loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
    loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages());
  }

  return extensionClasses;
}

// loadDirectory  读取目录查找文件

// loadResource 读取文件

// loadClass 加载类

// saveInExtensionClass 保存类信息到 Map  

二. Dubbo 中的依赖注入

Dubbo 依赖注入和核心逻辑是通过 Set 方法进行依赖注入,会在 Dubbo SPI 或者 Spring SPI 中查找对象。

// 依赖注入
private T injectExtension(T instance) {

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

  try {
    for (Method method : instance.getClass().getMethods()) {
      if (!isSetter(method)) {
        continue;
      }

      if (method.getAnnotation(DisableInject.class) != null) {
        continue;
      }
      // 获取 set 方法参数类型
      Class<?> pt = method.getParameterTypes()[0];
      if (ReflectUtils.isPrimitives(pt)) {
        continue;
      }

      try {
        // 获取 setXXX 中的 xxx
        String property = getSetterProperty(method);
        Object object = objectFactory.getExtension(pt, property); // Car.class, car
        if (object != null) {
          // 根据参数类型或属性名,从 objectFactory 中获取到对象,然后调用 set 方法进行依赖注入
          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;
}

// ExtensionLoader
private ExtensionLoader(Class<?> type) {
  this.type = type;
  // ExtensionLoader 表示拓展类实例工程,可以利用  ExtensionFactory 得到某个拓展的对象实例
  // 或者得到 ExtensionFactory 接口的 AdaptiveExtensionFactory 实例, 利用 AdaptiveExtensionFactory 来获取某个类的实例
  objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

// AdaptiveExtensionFactory 构造方法
public AdaptiveExtensionFactory() {
  // 支持那些 ExtensionFactory (Spring, SPI)
  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);
}

// SpringExtensionFactory#getExtension 在 Spring 容器中去查找Bean
public <T> T getExtension(Class<T> type, String name) {

  // 如果有 @SPI 修饰就不管
  if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
    return null;
  }

  // 首先 byName, 然后 byType 
  for (ApplicationContext context : CONTEXTS) {
    T bean = BeanFactoryUtils.getOptionalBean(context, name, type);
    if (bean != null) {
      return bean;
    }
  }
  return null;
}

// SpiExtensionFactory#getExtension
public <T> T getExtension(Class<T> type, String name) {
  // 接口上的 SPI 注解
  if (type.isInterface() && type.isAnnotationPresent(SPI.class)) {
    ExtensionLoader<T> loader = ExtensionLoader.getExtensionLoader(type);
    if (!loader.getSupportedExtensions().isEmpty()) {
      return loader.getAdaptiveExtension(); // 接口的 Adaptive 对象
    }
  }
  return null;
}

// ExtensionLoader
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;
}

// 获取 AdaptiveExtensionClass
private Class<?> getAdaptiveExtensionClass() {
  // 获取当前接口的所有拓展类
  getExtensionClasses();
  // 缓存了 @Adaptive 注解标记类
  if (cachedAdaptiveClass != null) {
    return cachedAdaptiveClass;
  }
  // 如果某个接口没有手动指定 Adaptive, 那么就生成一个 Adaptive 类
  return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

// 创建代理类
private Class<?> createAdaptiveExtensionClass() {
  // 生成代码
  String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
  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);
}

三. Dubbo 中的 AOP

dubbo中也实现了一套非常简单的 AOP,就是利用 Wrapper.class ,如果一个接口的扩展点中包含了多个 Wrapper 类,那么在实例化完某个扩展点后,就会利用这些 Wrapper 类对这个实例进行包裹,比如:现在有一个 DubboProtocol 的实例,同时对于 Protocol 这个接口还有很多的 Wrapper,比如 ProtocolFilterWrapper、ProtocolListenerWrapper,那么,当对 DubboProtocol 的实例完成了IOC之后,就会先调用 new ProtocolFilterWrapper(DubboProtocol实例) 生成一个新的Protocol的实例,再对此实例进行IOC,完了之后,会再调用new ProtocolListenerWrapper(ProtocolFilterWrapper实例)生成一个新的Protocol的实例,然后进行IOC,从而完成DubboProtocol实例的AOP。

// ExtensionLoader#createExtension
if (wrap) {

  List<Class<?>> wrapperClassesList = new ArrayList<>();
  if (cachedWrapperClasses != null) {
    wrapperClassesList.addAll(cachedWrapperClasses);
    wrapperClassesList.sort(WrapperComparator.COMPARATOR);
    Collections.reverse(wrapperClassesList);
  }

  if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
    for (Class<?> wrapperClass : wrapperClassesList) {
      Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
      if (wrapper == null
          || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
        instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
      }
    }
  }

四. Dubbo 中的 Adaptive 机制

每个扩展点都有一个name,通过这个name可以获得该name对应的扩展点实例,但是有的场景下,希望一次性获得多个扩展点实例,可以通过传入多个name来获取,可以通过识别URL上的信息来获取:

extensionLoader.getActivateExtension(url, new String[]{"car1", "car2"}); 这个可以拿到name为car1和car2的扩展类实例,同时还会通过传入的url寻找可用的扩展类, 怎么找的呢?

在一个扩展点类上,可以添加 @Activate 注解,这个注解的属性有:

  1. String[] group():表示这个扩展点是属于拿组的,这里组通常分为 PROVIDERCONSUMER,表示该扩展点能在服务提供者端,或者消费端使用
  2. String[] value():指示的是URL中的某个参数 key,当利用 getActivateExtension 方法来寻找扩展点时,如果传入的url中包含的参数的所有 key 中,包括了当前扩展点中的 value 值,那么则表示当前 url 可以使用该扩展。