Dubbo 良好的扩展性得益于它的 SPI 机制,在 Dubbo 中,几乎所有的功能组件都是基于 SPI 机制实现的。由于 Java SPI 机制存在一些问题,且无法满足 Dubbo 的需求,于是 Dubbo SPI 就在 Java SPI 的思想上做了改进,形成了一套自己的配置规范和特性。
改进 Java SPI 在加载插件的时候,会一次性地加载并实例化 扩展点所有的实现类,因此这个过程可能很耗时,同时可能有的类并没有用到,这也会造成浪费。而 Dubbo SPI 在加载插件的时候会缓存加载过的类和实例化后的对象,同时缓存的 Class 并不会全部实例化,而是按需实例化并缓存,因此性能更好。
当 Java SPI 加载插件失败时,可能会因为各种原因导致异常信息被“吞掉”,而 Dubbo SPI 在扩展加载失败的时候会先抛出真实的异常并打印日志。扩展点在被动加载的时候,即使有部分扩展加载失败也不会影响其他扩展点和整个框架的使用。
Dubbo SPI 还增加了对扩展的 IoC 和 AOP 的支持,一个扩展可以通过 setter 直接注入其他扩展。同时 Dubbo 还支持包装扩展类,它推荐把通用的抽象逻辑放到包装类中,用于实现扩展点的 AOP 特性。比如 ProtocolFilterWrapper 类就是一个包装类,它包装了一个 Protocol,将一些通用的判断逻辑全部放在了 export 方法中,但最终它还是会调用 Protocol#export 方法。这类似于代理模式,在被代理的类前后插入逻辑,以组合的方式实现功能增强。
1 2 3 4 5 6 public <T> Exporter<T> export (Invoker<T> invoker) throws RpcException { if (UrlUtils.isRegistry(invoker.getUrl())) { return protocol.export(invoker); } return protocol.export(buildInvokerChain(invoker, SERVICE_FILTER_KEY, CommonConstants.PROVIDER)); }
配置规范
规范
描述
SPI 配置文件路径
META-INF/services/、META-INF/dubbo/、META-INF/dubbo/internal/
SPI 配置文件名称
接口的全限定名
SPI 配置文件内容
key=value 形式,多个实现类使用换行符分隔
特性 Dubbo 的扩展类一共包含四种特性:自动包装、自动加载、自适应和自动激活。
自动包装 在使用 ExtensionLoader 加载扩展类时,如果发现这个扩展类包含其他扩展点作为构造函数的参数 ,则这个扩展类会被认为是一个 Wrapper 类。典型的比如 ProtocolFilterWrapper 扩展类。
1 2 3 4 5 6 7 8 9 10 11 public class ProtocolFilterWrapper implements Protocol { private final Protocol protocol; public ProtocolFilterWrapper (Protocol protocol) { if (protocol == null ) { throw new IllegalArgumentException ("protocol == null" ); } this .protocol = protocol; } }
ProtocolFilterWrapper 虽然实现了 Protocol 接口,但是其构造函数又注入了一个 Protocol 类型的参数,因此它会被认为是一个 Wrapper 类,类似于装饰器模式,把通用的抽象逻辑进行封装或对子类进行增强,让子类可以更加专注于具体的实现。
自动加载 除了在构造函数中传入其他扩展实例,我们还经常使用 setter 方法来设置属性值。如果某个扩展类是另外一个扩展点类的成员属性,并且该属性还拥有 setter 方法,那么 Dubbo 会自动注入对应的扩展点实例。ExtensionLoader 在执行扩展点初始化的时候,会自动通过 setter 方法注入对应的实例,这里有一个问题,如果扩展类属性是一个接口,他有多种实现,那么应该注入哪个呢?这就涉及到了 Dubbo SPI 扩展类的第三个特性——自适应。
自适应 使用 @Adaptive
注解,可以动态地通过 URL 中的参数来确定要使用哪个具体的实现类,从而解决自动加载中的实例注入问题。比如在 Transporter 接口中:
1 2 3 4 5 6 7 8 @SPI("netty") public interface Transporter { @Adaptive({Constants.SERVER_KEY, Constants.TRANSPORTER_KEY}) RemotingServer bind (URL url, ChannelHandler handler) throws RemotingException; @Adaptive({Constants.CLIENT_KEY, Constants.TRANSPORTER_KEY}) Client connect (URL url, ChannelHandler handler) throws RemotingException; }
这里的 @Adaptive
注解中传入了两个参数,分别为 server 和 transporter,在外部调用 Transporter#bind 方法时,会动态地从传入的参数 URL 中提取 key 为 server 的 value 值,如果无法匹配则会继续提取 key 为 transporter 的 value 值。只有在都没有匹配时,才会使用 @SPI
注解中的默认值去匹配,此时如果无法匹配就会抛出异常。
自动激活 通过自适应的方式来寻找实现类会比较灵活,但是只能激活一个具体的实现类,如果需要激活多个实现类,或者需要根据不同的条件同时激活多个实现类,这就需要使用自动激活特性。使用 @Activate
注解,可以标记对应的扩展点默认被激活启用,该注解可以通过传入不同的参数,设置扩展点在不同的条件下被自动激活。
扩展点注解 目前能够使用的扩展点注解有三种:@SPI
、@Adaptive
和 @Activate
。
@SPI
@SPI
注解一般用在接口上,它的主要作用就是标记这个接口是一个 Dubbo SPI 接口,即是一个扩展点,可以有多个不同的内置或者用户自定义的实现。该注解可以传入一个 String 类型的参数,表示该接口的默认实现类,比如 Transporter 接口的传入的参数为 netty,对应的配置为:netty=org.apache.dubbo.remoting.transport.netty4.NettyTransporter
。
1 2 3 4 @SPI("netty") public interface Transporter { ... }
@Adaptive
@Adaptive
注解可以标记在类、接口、枚举类和方法上,但是整个 Dubbo 框架中,只有很少的几个地方使用在类上,比如 AdaptiveExtensionFactory 和 AdaptiveCompiler,其余都标注在方法上。方法级别的注解在第一次使用 ExtensionLoader#getExtension 方法时,会自动生成和编译一个动态的 Adaptive 类 ,从而达到动态指定实现类的效果。
比如 Transporter 接口在 bind 和 connect 方法上都使用了该注解,在初始化扩展点时,会生成一个 Transporter$Adaptive 类,其中会实现这两个方法,方法中会通过 URL 找到并调用真正的实现类。下面的源代码可以使用阿里开源的 Java 诊断工具 Arthas 在 Dubbo 服务运行时通过反编译获得。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class Transporter$Adaptive implements Transporter { @Override public Client connect (URL uRL, ChannelHandler channelHandler) throws RemotingException { if (uRL == null ) { throw new IllegalArgumentException ("url == null" ); } URL uRL2 = uRL; String string = uRL2.getParameter("client" , uRL2.getParameter("transporter" , "netty" )); if (string == null ) { throw new IllegalStateException (new StringBuffer ().append("Failed to get extension (org.apache.dubbo.remoting.Transporter) name from url (" ).append(uRL2.toString()).append(") use keys([client, transporter])" ).toString()); } Transporter transporter = ExtensionLoader.getExtensionLoader(Transporter.class).getExtension(string); return transporter.connect(uRL, channelHandler); } @Override public RemotingServer bind (URL uRL, ChannelHandler channelHandler) throws RemotingException { if (uRL == null ) { throw new IllegalArgumentException ("url == null" ); } URL uRL2 = uRL; String string = uRL2.getParameter("server" , uRL2.getParameter("transporter" , "netty" )); if (string == null ) { throw new IllegalStateException (new StringBuffer ().append("Failed to get extension (org.apache.dubbo.remoting.Transporter) name from url (" ).append(uRL2.toString()).append(") use keys([server, transporter])" ).toString()); } Transporter transporter = ExtensionLoader.getExtensionLoader(Transporter.class).getExtension(string); return transporter.bind(uRL, channelHandler); } }
当 @Adaptive
注解放在实现类上,则整个实现类会直接作为默认实现,不会再自动生成 Adaptive 类。在扩展点初始化时,如果发现实现类上有 @Adaptive
注解,那么会将该类直接赋值给 cachedAdaptiveClass
,后续实例化的时候,就不会再动态生成代码,而是直接实例化并缓存到 cachedAdaptiveInstance
中。在扩展点接口的多个实现类中,只能有一个实现类上可以添加 @Adaptive
注解。
另外,如果包装类没有使用 @Adaptive
注解指定 key 值,也没有填写 @SPI
注解的默认值,那么 Dubbo 会自动把接口名称根据驼峰分开,并用 .
符号连接,以此来作为默认实现类的名称,比如 org.apache.dubbo.xxx.YyyInvokerWrapper
中的 YyyInvokerWrapper 会被转换成 yyy.invoker.wrapper
。
@Activate
@Activate
注解可以标记在类、接口、枚举类和方法上,主要用在有多个扩展点实现、需要根据不同条件激活多个实现的场景,比如 Filter。@Activate
注解可以传入的参数有很多。
参数
描述
String[] group()
URL 中的分组,如果匹配则激活,可以设置多个
String[] value()
URL 中含有该 key 则激活
String[] before()
扩展点列表,表示哪些扩展点要在本扩展点之前
String[] after()
扩展点列表,表示哪些扩展点要在本扩展点之后
int order()
排序
ExtensionLoader ExtensionLoader 是整个扩展机制的主要逻辑类,通过它可以实现配置(META-INF 目录下的配置文件)的加载、扩展类和实例的缓存、自适应对象的生成等。
ExtensionLoader 的逻辑入口可以分为三个:getExtension、getAdaptiveExtension 和 getActivateExtension,分别用来获取普通的扩展类实例、自适应扩展类实例和自动激活的扩展类实例列表。
getExtension 大概的流程就是先从 cachedInstances 缓存中获取类实例,如果缓存中没有就先通过配置文件加载扩展类,然后实例化对应的扩展类并放入缓存,然后再处理 setter 依赖注入和 Wrapper 的构造器注入,最后根据是否实现了 LifeCycle 接口决定是否调用扩展类的 initialize 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public T getExtension (String name) { 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 ) { synchronized (holder) { instance = holder.get(); if (instance == null ) { instance = createExtension(name); holder.set(instance); } } } return (T) instance; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 private T createExtension (String name) { Class<?> clazz = getExtensionClasses().get(name); if (clazz == null ) { throw findException(name); } try { T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null ) { EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } injectExtension(instance); Set<Class<?>> wrapperClasses = cachedWrapperClasses; if (CollectionUtils.isNotEmpty(wrapperClasses)) { for (Class<?> wrapperClass : wrapperClasses) { instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } initExtension(instance); return instance; } catch (Throwable t) { throw new IllegalStateException ("Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: " + t.getMessage(), t); } }
getAdaptiveExtension 大概的流程就是先从 cachedAdaptiveInstance 缓存中获取实例,如果缓存中没有就先通过配置文件加载扩展类,如果扩展类上使用了 @Adaptive
注解则该扩展类为默认实现,否则就使用代码生成器生成类似 Transporter$Adaptive 这种的自适应扩展类,拿到 Class 之后就进行实例化,然后进行依赖注入,最终放入缓存并返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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; }
1 2 3 4 5 6 7 8 9 10 private T createAdaptiveExtension () { try { return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException ("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e); } }
1 2 3 4 5 6 7 8 9 10 11 private Class<?> getAdaptiveExtensionClass() { getExtensionClasses(); if (cachedAdaptiveClass != null ) { return cachedAdaptiveClass; } return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
1 2 3 4 5 6 7 8 9 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); }
getActivateExtension 该方法有多个重载方法,我们关注参数最多的那个即可,大体上就是将缓存过的 @Activate
集合根据传入的条件进行筛选,最终使用获取普通扩展类实例的 getExtension 方法获取符合条件的扩展类实例即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 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); } ... return activateExtensions; }
ExtensionFactory 如果我们单独使用 Dubbo 的 SPI 机制,可以先定义接口,然后添加配置文件(META-INF 目录下的配置文件),最后通过 ExtensionLoader 加载。
1 2 3 4 5 6 7 public class Test { public static void main (String[] args) { UserService userService = ExtensionLoader.getExtensionLoader(UserService.class) .getDefaultExtension(); userService.getAll(); } }
可以看到,在使用 ExtensionLoader 的时候,需要先通过 getExtensionLoader 方法获取 ExtensionLoader,在这个方法中会对 ExtensionLoader 进行初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public static <T> ExtensionLoader<T> getExtensionLoader (Class<T> type) { if (type == null ) { throw new IllegalArgumentException ("Extension type == null" ); } if (!type.isInterface()) { throw new IllegalArgumentException ("Extension type (" + type + ") is not an interface!" ); } if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException ("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!" ); } ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null ) { 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 = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }
可以看到 ExtensionLoader 中有一个比较关键的属性:objectFactory,它在 ExtensionLoader 实例化的时候会通过 getAdaptiveExtension 方法获取 ExtensionFactory 接口的实现类。ExtensionFactory 有三个实现类:SpiExtensionFactory、SpringExtensionFactory 和 AdaptiveExtensionFactory,其中 AdaptiveExtensionFactory 类上使用了 @Adaptive
注解,因此它是默认的实现类。那么 ExtensionFactory 到底是干什么用的呢?我们可以全局搜索一下,发现它在 injectExtension 方法中被使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 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 ; } 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; }
在获取扩展类实例时,如果扩展类实例的属性包含其他扩展类实例,那么就会通过 ExtensionFactory#getExtension 方法加载,加载的范围包括 Dubbo 自身缓存的扩展类实例以及 Spring 容器实例 ,这也就意味着,我们可以在 Dubbo 的扩展类实例中使用其他扩展类实例和 Spring 容器中的实例。