Dubbo内核之SPI

167 阅读4分钟

Dubbo内核之SPI(1)

SPI是什么

SPI全程Service Provider Interface,是java提供的用来被第三方实现或者扩展的API,可以用来启用框架扩展或者替换组件

使用场景

比较常见的例子:

  1. 数据库驱动加载接口实现类
  2. 日志门面接口实现类加载
  3. dubbo中SPI的利用

使用介绍

  1. 编写标准服务接口,并且服务提供者实现对应接口
public interface HelloService {
    void sayHello();
}
public class AHelloServiceImpl implements HelloService{
    @Override
    public void sayHello() {
        System.out.println("A say hello");
    }
}
public class BHelloServiceImpl implements HelloService{
    @Override
    public void sayHello() {
        System.out.println("B say Hello");
    }
}
  1. 在META-INF下面新增一个文件夹services,在路径下新建一个名称为接口全限定路径名的文件,至于为什么是META-INF/servies,因为通过ServiceLoader类里面写死了路径,如下图: 文件中的内容为接口具体实现类的全限定路径名:
com.loup.dubbo.test.spi.AHelloServiceImpl
com.loup.dubbo.test.spi.BHelloServiceImpl
  1. 通过ServiceLoader.load()去加载对应接口的实现类,ServiceLoader实现了Iterable,所以是可迭代循环的,而且ServiceLoader也是通过循环去加载所有的资源文件,最后执行接口如下:

Dubbo中SPI的实现

dubbo中的SPI对于Java中原生的SPI机制做了优化,接口文件中使用key-value的形式,dubbo不仅只加载META-INF/services下面的文件,还有META-INF/dubbo和META-INF/dubbo/internal/。dubbo中加载是用ExtensionLoader

dubbo对java的SPI做了改进优化

  • JDK标准的SPI会一次性实例化扩展点所有实现,如果有扩展实现初始化很耗时,但如果没用上也加载,会很浪费资源。
  • 增加了对扩展点IoC和AOP的支持,一个扩展点可以直接setter注入其它扩展点。
  • java中如果扩展点加载失败,连扩展点的名称都拿不到了

ExtensionLoader

  1. ExtensionLoder.getExtensionLoader(Class type); ExtensionLoader.getExtensionLoader(Protocol.class)

每个有SPI注解的接口使用ExtensionLoder加载,都会创建ExtensionLoader实例,放在ConcurrentMap<Class, ExtensionLoader> EXTENSION_LOADERS缓存中

扩展点

静态扩展点

ExtensionLoader loader = ExtensionLoader.getExtensionLoader(Protocol.class);
loader.getExtension("dubbo");
  1. loader.getExtension("dubbo");会加载到META-INF/dubbo/internal/org.apache.dubbo.rpc.Protocol文件,里面内容是dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol,key为dubbo,value为接口的实现类全路径名称,加载完后以dubbo名称为key放在map缓存中,然后通过get(name)获取类,然后反射生成对应接口实现类的实例,放在EXTENSION_INSTANCES缓存中,方法大致流程调用如下:

自适应扩展点

loader.getAdaptiveExtension();

通过上面getAdaptiveExtension获取自适应默认扩展点,自适应扩展点有两种,一种是方法级别的,一种是类级别的

类级别的自适应

dubbo中类级别的自适应不多,据我所知就两个AdaptiveExtensionFactory和AdaptiveCompiler,如下Compiler获取自适应扩展点

ExtensionLoader.getExtensionLoader(Compiler.clss).getAdaptiveExtension();
public T getAdaptiveExtension() {
			......
            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 {
		// injectExtension方法暂时不看,此方法时依赖注入用的,因为类级别的自适应是手动创建的类,不排除其中有依赖其他扩展点
// 此刻主要关注的是getAdaptiveExtensionClass方法,会通过反射生成对应类的实例,方法中会调用getExtensionClasses,主要逻辑还是要看getExtensionClasses方法
            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();
        if (cachedAdaptiveClass != null) {
            return cachedAdaptiveClass;
        }
        return cachedAdaptiveClass = createAdaptiveExtensionClass();
    }
  // 最后还是回到loadExtensionClasses方法,加载配置文件中的接口对应实现类,加载的时候会判断接口实现类是否有类级别的@Adaptive注解,如果有,则把当前类缓存到cachedAdaptiveClass中,getAdaptiveExtensionClass方法会判断此字段是否为空
  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;
    }
方法级别
ExtensionLoader.getExtensionLoader(Protocol.clss).getAdaptiveExtension();
基本逻辑跟方法级别一样,只是方法级别的读取配置文件时,没有类有注解@Adaptive注解,所以getAdaptiveExtensionClass中判断的cachedAdaptiveClass就为空,走到createAdaptiveExtensionClass()
private Class<?> createAdaptiveExtensionClass() {
      // 此处生成一个XX$Adaptive的自适应类
  	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);
  }
// Protocol.class接口中export()和refer()方法有@Adaptive注解,dubbo生成的类如下:
import org.apache.dubbo.common.extension.ExtensionLoader;
public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol {
  public void destroy() {
      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!");
  }
  public int getDefaultPort() {
      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!");
  }
  public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException {
      if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null");
      if (arg0.getUrl() == null)
          throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null");
      org.apache.dubbo.common.URL url = arg0.getUrl();
      String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
      if (extName == null)
          throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
      org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
      return extension.export(arg0);
  }
  public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException {
      if (arg1 == null) throw new IllegalArgumentException("url == null");
      org.apache.dubbo.common.URL url = arg1;
      String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol());
      if (extName == null)
          throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])");
      org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName);
      return extension.refer(arg0, arg1);
  }
  public java.util.List getServers() {
      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!");
  }
}

其实借鉴dubbo中此种处理方法,代码中要是if-else过多,可以采用此种方法改造,当然使用策略+工厂模式也是中方法。看源码看的是其中思想。