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 容器中的实例。