Dubbo SPI(Service Provider Interface)增强了 JDK 中提供的标准 SPI 功能,在 Dubbo 中的很多核心接口都是通过实现扩展点接口来提供服务的。Dubbo 解决了 JDK 标准的 SPI 的以下问题:

  • JDK 标准的 SPI 会一次性实例化扩展点的所有实现类,如果有些扩展点实现类初始化很耗时,但当前又没用上它的功能,那么加载就很浪费资源。而 Dubbo SPI 则是在具体用某一个实现类时才会对具体实现类进行实例化。
  • JDK 标准的 SPI 如果某个扩展点加载失败,是不会友好地向用户通知具体异常。
  • Dubbo SPI 增加了对扩展点 IoC 和 AOP 的支持,一个扩展点可以直接使用 setter 方法注入其他扩展点,也可以对扩展点使用 Wrapper 类进行功能增强。

自定义扩展点

下面以 Dubbo 的 Protocol 接口为例,介绍如何自定义扩展点实现:

  1. @SPI("dubbo")
  2. public interface Protocol {
  3. ...
  4. }

扩展点接口的类都含有 @SPI 注解,这里注解中的 dubbo 值说明 Protocol 扩展接口 SPI 的默认实现为 DubboProtocol。如果我们想自己写一个 Protocol 扩展接口的实现类,则需要在实现类所在的 Jar 包内的 META-INF/dubbo/ 目录下创建一个名为 org.apache.dubbo.rpc.Protocol 的文本文件,内容为:

  1. myprotocol=com.xxx.xxx.MyProtocol

那如何使用我们自定义的扩展实现呢?在 Dubbo 配置模块中,扩展点接口均有对应的配置属性或 XML 标签,我们可以在指定组件的 name 属性上赋值我们自定义的实现类名称,如下通过标签指定了使用哪个扩展:

  1. <dubbo:protocol name="myprotocol" />

注意,这里的 name 必须要与 META-INF/dubbo/ 目录下的 org.apache.dubbo.rpc.Protocol 配置文件中的等号左侧的 key 的名字一致。

1. IOC 自动注入

当通过 ExtensionLoader 获取扩展接口实例时,在创建完对象实例后还会调用 injectExtension 方法来注入其依赖的扩展点实例,具体代码如下:

  1. private T injectExtension(T instance) {
  2. try {
  3. // 遍历该对象实例的所有 setter 方法
  4. for (Method method : instance.getClass().getMethods()) {
  5. if (!isSetter(method)) {
  6. continue;
  7. }
  8. // 如果某个setter方法被@DisableInject注解修饰,表示不需要自动注入
  9. if (method.getAnnotation(DisableInject.class) != null) {
  10. continue;
  11. }
  12. // 如果第一个参数类型是原始类型,则不需要自动注入
  13. Class<?> pt = method.getParameterTypes()[0];
  14. if (ReflectUtils.isPrimitives(pt)) {
  15. continue;
  16. }
  17. // 查看setter方法设置的变量是不是有扩展接口实现
  18. try {
  19. // 获取setter对应的属性名,比如 setVersion,则返回 version
  20. String property = getSetterProperty(method);
  21. // 查看该属性是否存在扩展实现
  22. Object object = objectFactory.getExtension(pt, property);
  23. // 如果存在则反射调用setter方法进行属性赋值
  24. if (object != null) {
  25. method.invoke(instance, object);
  26. }
  27. } catch (Exception e) {
  28. logger.error("Failed to inject via method " + method.getName() + " of interface " + type.getName() + ": " + e.getMessage(), e);
  29. }
  30. }
  31. } catch (Exception e) {
  32. logger.error(e.getMessage(), e);
  33. }
  34. return instance;
  35. }

2. AOP 增强

在 Spring AOP 中,我们可以使用多个切面对指定类的方法进行增强,在 Dubbo 中也提供了类似的功能。在 Dubbo 中你可以指定多个 Wrapper 类对指定的扩展点的实现类的方法进行增强。

那 Wrapper 类是如何被加载的呢?

  1. private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name,
  2. boolean overridden) throws NoSuchMethodException {
  3. ......
  4. if (isWrapperClass(clazz)) {
  5. cacheWrapperClass(clazz);
  6. }
  7. ......
  8. }
  9. // Wrapper类必须有一个原始类型入参的构造器
  10. private boolean isWrapperClass(Class<?> clazz) {
  11. try {
  12. clazz.getConstructor(type);
  13. return true;
  14. } catch (NoSuchMethodException e) {
  15. return false;
  16. }
  17. }

可以看到,当在加载扩展接口的实现类的时候,如果配置文件中指定的类的全限定名对应的是一个 Wrapper 类,则会将其缓存在 cachedWrapperClasses 中。之后,在实例化对象实例时,会将 Wrapper 类也进行实例化,并将原始类型的对象实例通过 IOC 机制注入到 Wrapper 实例中,之后返回的就是这个 Wrapper 实例。

  1. private T createExtension(String name, boolean wrap) {
  2. Class<?> clazz = getExtensionClasses().get(name);
  3. ......
  4. // 如果有包装类型
  5. if (wrap) {
  6. List<Class<?>> wrapperClassesList = new ArrayList<>();
  7. if (cachedWrapperClasses != null) {
  8. wrapperClassesList.addAll(cachedWrapperClasses);
  9. // 根据@Activate注解中的order属性排序
  10. wrapperClassesList.sort(WrapperComparator.COMPARATOR);
  11. Collections.reverse(wrapperClassesList);
  12. }
  13. if (CollectionUtils.isNotEmpty(wrapperClassesList)) {
  14. for (Class<?> wrapperClass : wrapperClassesList) {
  15. // @Wrapper注解可提供一些判断条件
  16. Wrapper wrapper = wrapperClass.getAnnotation(Wrapper.class);
  17. if (wrapper == null
  18. || (ArrayUtils.contains(wrapper.matches(), name) && !ArrayUtils.contains(wrapper.mismatches(), name))) {
  19. // 通过IOC机制注入原始类型对应的实例对象
  20. instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
  21. }
  22. }
  23. }
  24. }
  25. ......
  26. }

总结一下,Wrapper 包装类必须实现扩展接口,并且要提供一个原接口实现类对象的构造器,这样就可以在调用接口方法前后,注入我们自定义的逻辑,并可以通过原接口实现类完成原始逻辑的执行。

下面以 ProtocolFilterWrapper 为例讲解一下,该类属于 Protocol 接口实现类的包装类:

  1. // 通过@Activate注解定义执行该Wrapper顺序
  2. @Activate(order = 100)
  3. public class ProtocolFilterWrapper implements Protocol {
  4. private final Protocol protocol;
  5. // 有一个原始接口实现类的实例的入参的构造器
  6. public ProtocolFilterWrapper(Protocol protocol) {
  7. if (protocol == null) {
  8. throw new IllegalArgumentException("protocol == null");
  9. }
  10. this.protocol = protocol;
  11. }
  12. private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
  13. Invoker<T> last = invoker;
  14. List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
  15. if (!filters.isEmpty()) {
  16. for (int i = filters.size() - 1; i >= 0; i--) {
  17. final Filter filter = filters.get(i);
  18. last = new FilterNode<T>(invoker, last, filter);
  19. }
  20. }
  21. return last;
  22. }
  23. @Override
  24. public int getDefaultPort() {
  25. // 不进行扩展,直接调用原方法
  26. return protocol.getDefaultPort();
  27. }
  28. @Override
  29. public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
  30. if (UrlUtils.isRegistry(invoker.getUrl())) {
  31. return protocol.export(invoker);
  32. }
  33. // 在执行原始的export方法前,调用Filter拦截器链
  34. return protocol.export(buildInvokerChain(invoker, SERVICE_FILTER_KEY, CommonConstants.PROVIDER));
  35. }
  36. ......
  37. }

该 Wrapper 在 META-INF/dubbo/internal 路径下的 org.apache.dubbo.rpc.Protocol 文件中进行了定义:

  1. filter=org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper

@Adaptive 适配器

Dubbo 框架在使用扩展点功能时是对接口进行依赖的,而一个扩展接口对应了一系列的扩展实现类。那么如何选择使用哪一个扩展接口的实现类呢?其实是使用适配器模式来做的。还是以 Protocol 接口为例:

  1. @SPI("dubbo")
  2. public interface Protocol {
  3. @Adaptive
  4. <T> Exporter<T> export(Invoker<T> invoker) throws RpcException;
  5. @Adaptive
  6. <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException;
  7. }

可以看到,在方法上修饰了 @Adaptive 注解,这个注解表示对应的方法可以被适配器所适配。当调用 ExtensionLoader 类的 getAdaptiveExtension 方法时,Dubbo 会使用动态编译技术为接口 Protocol 生成一个适配器类 Protocol$Adaptive 的对象实例,这个适配类的内容如下:

  1. package org.apache.dubbo.rpc;
  2. import org.apache.dubbo.common.extension.ExtensionLoader;
  3. public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
  4. public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
  5. if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
  6. if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
  7. org.apache.dubbo.common.URL url = arg0.getUrl();
  8. String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
  9. if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
  10. org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
  11. return extension.export(arg0);
  12. }
  13. public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
  14. if (arg1 == null) throw new IllegalArgumentException("url == null");
  15. org.apache.dubbo.common.URL url = arg1;
  16. String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() );
  17. if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
  18. org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
  19. return extension.refer(arg0, arg1);
  20. }
  21. public java.util.List getServers() {
  22. throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
  23. }
  24. public void destroy() {
  25. throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
  26. }
  27. public int getDefaultPort() {
  28. throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!");
  29. }
  30. }

可以看到,这个适配器类只会针对修饰了 @Adaptive 注解的方法进行适配,其他方法直接抛出异常。在 Dubbo 框架中需要使用 Protocol 的实例时,实际上就是使用 Protocol$Adaptive 这个对象实例来获取具体的 SPI 实现类的。在适配类的 export 和 refer 方法中,会根据 Invoker 中 URL 里面的协议类型参数,通过 getExtension 方法根据扩展名称获取对应的扩展实现类,再调用其方法。这样,适配器类就可以在每次方法调用时,根据传递的协议参数的不同,加载不同的 Protocol 的 SPI 实现,具有极大的灵活性。

@Adaptive 注解可以修饰类和方法上,在修饰类的时候不会生成代理类,因为被修饰的这个类就作为代理类,当修饰在方法上的时候会生成代理类。生成代理类的源码如下:

  1. private Class<?> createAdaptiveExtensionClass() {
  2. // 生成代码
  3. String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
  4. ClassLoader classLoader = findClassLoader();
  5. org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
  6. // 编译成Class
  7. return compiler.compile(code, classLoader);
  8. }

@Activate

ExtensionLoader 不仅提供了获取某一个扩展接口实现类的方法,还提供了获取扩展接口全部实现类的方法。比如 ProtocolFilterWrapper 中的 buildInvokerChain 方法在建立 Filter 责任链时,需要把属于某一 group 的所有 Filter 都放到责任链里,其通过如下方法来获取属于某个组的 Filter 扩展实现类的:

  1. List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);

比如,当服务提供端启动时只会加载 group 为 provider 的 Filter 扩展实现类:

  1. @Activate(group = CommonConstants.PROVIDER, value = EXECUTES_KEY)
  2. public class ExecuteLimitFilter implements Filter, Filter.Listener {
  3. ......
  4. }

当服务消费端启动时只会加载 group 为 consumer 的 Filter 扩展实现类:

  1. @Activate(group = CONSUMER, value = ACTIVES_KEY)
  2. public class ActiveLimitFilter implements Filter, Filter.Listener {
  3. ......
  4. }

@Activate 注解有三个属性,group 表示修饰在哪个端,是 provider 还是 consumer,value 表示在 URL 参数中出现才会被激活,order 表示实现类的顺序。只有 group 和 value 都满足,该 Filter 才会被执行。

下面分析下 getActivateExtension 方法的具体逻辑:

  1. public List<T> getActivateExtension(URL url, String key, String group) {
  2. String value = url.getParameter(key);
  3. return getActivateExtension(url, StringUtils.isEmpty(value) ? null : COMMA_SPLIT_PATTERN.split(value), group);
  4. }
  5. public List<T> getActivateExtension(URL url, String[] values, String group) {
  6. List<T> activateExtensions = new ArrayList<>();
  7. List<String> names = values == null ? new ArrayList<>(0) : asList(values);
  8. if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) {
  9. // 获取扩展接口对应的所有扩展实现
  10. getExtensionClasses();
  11. // 在获取扩展实现的过程中,会解析@Activate注解信息
  12. // cachedActivates的key为扩展接口实现类的扩展名,value为扩展接口实现类上的@Activate注解元信息
  13. for (Map.Entry<String, Object> entry : cachedActivates.entrySet()) {
  14. String name = entry.getKey();
  15. Object activate = entry.getValue();
  16. String[] activateGroup, activateValue;
  17. // 如果含有@Activate注解,则获取注解的group和value值
  18. if (activate instanceof Activate) {
  19. activateGroup = ((Activate) activate).group();
  20. activateValue = ((Activate) activate).value();
  21. } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) {
  22. // 兼容
  23. activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group();
  24. activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value();
  25. } else {
  26. continue;
  27. }
  28. // 如果当前扩展实现的组与我们传递的group匹配,且value值在URL组存在
  29. if (isMatchGroup(group, activateGroup)
  30. && !names.contains(name)
  31. && !names.contains(REMOVE_VALUE_PREFIX + name)
  32. && isActive(activateValue, url)) {
  33. activateExtensions.add(getExtension(name));
  34. }
  35. }
  36. activateExtensions.sort(ActivateComparator.COMPARATOR);
  37. }
  38. ......
  39. return activateExtensions;
  40. }