JDK自带SPI机制与Dubbo的SPI机制对比

原文地址:《JDK自带SPI机制与Dubbo的SPI机制对比》

1. JDK自带SPI机制

(1) 简介

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以在不改动接口源代码的情况下,通过 SPI 机制为我们的程序提供拓展功能。

(2) 编写示例

新建maven工程后,先定义一个接口


   
   
  1. public interface UploadCDN {
  2.     void upload (String url);
  3. }

接下来定义两个实现类


   
   
  1. public class QiyiCDN implements UploadCDN {
  2.     @Override
  3.     public void upload (String url) {
  4.        System.out.println( "upload to Qiyi");
  5.   }
  6. }
  7. public class ChinaNetCDN implements UploadCDN {
  8.     @Override
  9.     public void upload (String url) {
  10.        System.out.println( "upload to ChinaNetCDN");
  11.   }
  12. }

在resource/META-INF/services下创建一个文本文件,文件名为接口的全限定名:com.lzq.spidemo.service.UploadCDN,并写入以下内容


   
   
  1. com.lzq.spidemo.service.Impl.QiyiCDN
  2. com.lzq.spidemo.service.Impl.ChinaNetCDN

开始编写测试代码


   
   
  1. public class Demo {
  2.     public static void main (String[] args) {
  3.         //注意:"META-INF/services/"这个目录是在ServiceLoader写死的
  4.        ServiceLoader<UploadCDN> uploadCDN = ServiceLoader.load(UploadCDN.class);
  5.         for (UploadCDN u : uploadCDN) {
  6.            u.upload( "filePath");
  7.       }
  8.   }
  9. }

可以看到,所有的实现类都会创建新实例并调用对应方法。

(3) ServiceLoader源码

load函数会将当前线程的类加载器和接口的类对象传入ServiceLoader对象


   
   
  1. public static <S> ServiceLoader<S> load (Class<S> service) {
  2.     ClassLoader cl = Thread.currentThread().getContextClassLoader();
  3.     return ServiceLoader.load(service, cl);
  4. }

创建ServiceLoader对象后,会初始化一个内部的迭代器对象LazyIterator


   
   
  1. private class LazyIterator
  2.         implements Iterator<S>
  3.   {
  4.        Class<S> service;
  5.        ClassLoader loader;
  6.        Enumeration<URL> configs = null;
  7.        Iterator<String> pending = null;
  8.         String nextName = null;
  9.         private LazyIterator (Class<S> service, ClassLoader loader) {
  10.             this.service = service;
  11.             this.loader = loader;
  12.       }
  13.         private boolean hasNextService () {
  14.             if (nextName != null) {
  15.                 return true;
  16.           }
  17.             if (configs == null) {
  18.                 try {
  19.                     //查找到对应接口的文本文件,然后解析获取到其中记录的所有实现类
  20.                     String fullName = PREFIX + service.getName();
  21.                     if (loader == null)
  22.                        configs = ClassLoader.getSystemResources(fullName);
  23.                     else
  24.                         configs = loader.getResources(fullName);
  25.               } catch (IOException x) {
  26.                    fail(service, "Error locating configuration files", x);
  27.               }
  28.           }
  29.             while ((pending == null) || !pending.hasNext()) {
  30.                 if (!configs.hasMoreElements()) {
  31.                     return false;
  32.               }
  33.                pending = parse(service, configs.nextElement());
  34.           }
  35.            nextName = pending.next();
  36.             return true;
  37.       }
  38.      
  39.   }

2.Dubbo的SPI机制

(1) 简介

dubbo的spi在jdk的spi基础上做了扩展,可以指定或者某个实现类

(2) 实例

先定义一个接口LoadBalance, 这里的@SPI作用于接口类上,用来指定默认的实现类标识。

@Adaptive表名该方法会被代理动态实现。


   
   
  1. @SPI("demo")
  2. public interface LoadBalance {
  3.     @Adaptive
  4.     void Hello ();
  5. }

定义两个实现类


   
   
  1. public class DemoLoadbalance implements LoadBalance {
  2.     @Override
  3.     public void Hello () {
  4.        System.out.println( "this is demo balance");
  5.   }
  6. }
  7. public class TestLoadBalance implements LoadBalance {
  8.     @Override
  9.     public void Hello () {
  10.        System.out.println( "this is test balance");
  11.   }
  12. }

在resource/META-INF/services下创建一个文本文件,文件名为接口的全限定名:

com.lzq.dubbospidemo.service.LoadBalance,并写入以下内容,注意这里和jdk的写法不一样,前面需要写上标识


   
   
  1. demo=com.lzq.dubbospidemo.service. impl.DemoLoadbalance
  2. test=com.lzq.dubbospidemo.service. impl.TestLoadBalance

开始测试


   
   
  1. public static void main (String[] args) {
  2. ExtensionLoader<LoadBalance> extensionLoader = ExtensionLoader.getExtensionLoader(LoadBalance.class);
  3. LoadBalance demoBalance = extensionLoader.getExtension( "demo");
  4. demoBalance.Hello();
  5. LoadBalance testBalance = extensionLoader.getExtension( "test");
  6. testBalance.Hello();
  7. LoadBalance balance = extensionLoader.getDefaultExtension();
  8. balance.Hello();
  9. }

测试结果如图

3. 源码解析

先看一下获取ExtensionLoader的过程


   
   
  1. public static <T> ExtensionLoader<T> getExtensionLoader (Class<T> type) {
  2. if (type == null) {
  3. //不能传入null参数
  4. throw new IllegalArgumentException( "Extension type == null");
  5. } else if (!type.isInterface()) {
  6. //需要是接口
  7. throw new IllegalArgumentException( "Extension type(" + type + ") is not interface!");
  8. } else if (!withExtensionAnnotation(type)) {
  9. //需要带有SPI注解
  10. throw new IllegalArgumentException( "Extension type(" + type + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
  11. } else {
  12. //EXTENSION_LOADERS是一个可以并发访问的map对象,在这里相当于一个缓存
  13. //下面的步骤首先尝试从缓存获取loader对象,若缓存中不存在则新建loader对象
  14. //放入缓存,然后再次从缓存中获取
  15. ExtensionLoader<T> loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
  16. if (loader == null) {
  17. EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type));
  18. loader = (ExtensionLoader)EXTENSION_LOADERS.get(type);
  19. }
  20. return loader;
  21. }
  22. }

下面看看获取具体实现类的过程


   
   
  1. public T getExtension (String name) {
  2. if (name != null && name.length() != 0) {
  3. //参数为true,使用默认的extension
  4. if ( "true".equals(name)) {
  5. return this.getDefaultExtension();
  6. } else {
  7. //和上面一样,先查看缓存是否有对应的实例对象,没有就新建然后再获取
  8. //这里Holder对象持有一个volatile的value属性,保证了对所有线程的可见性
  9. Holder<Object> holder = (Holder) this.cachedInstances.get(name);
  10. if (holder == null) {
  11. this.cachedInstances.putIfAbsent(name, new Holder());
  12. holder = (Holder) this.cachedInstances.get(name);
  13. }
  14. Object instance = holder.get();
  15. if (instance == null) {
  16. //个人认为这里的holder对象主要是减小锁的粒度。
  17. synchronized(holder) {
  18. //两次检查获取,所以这里是线程安全
  19. //这里有点像懒汉式单例模式的创建
  20. instance = holder.get();
  21. if (instance == null) {
  22. instance = this.createExtension(name);
  23. holder.set(instance);
  24. }
  25. }
  26. }
  27. return instance;
  28. }
  29. } else {
  30. throw new IllegalArgumentException( "Extension name == null");
  31. }
  32. }

创建实例的过程如下:


   
   
  1. private T createExtension (String name) {
  2. //这里会先从缓存中查找class对象,如果没有的话就会从配置文件中加载所有的扩展类,最后得到扩展名与对应类的map,详见loadExtensionClasses函数
  3. Class<?> clazz = (Class) this.getExtensionClasses().get(name);
  4. if (clazz == null) {
  5. throw this.findException(name);
  6. } else {
  7. try {
  8. T instance = EXTENSION_INSTANCES.get(clazz);
  9. if (instance == null) {
  10. EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance());
  11. instance = EXTENSION_INSTANCES.get(clazz);
  12. }
  13. // 向实例中注入依赖,IOC的实现
  14. this.injectExtension(instance);
  15. Set<Class<?>> wrapperClasses = this.cachedWrapperClasses;
  16. Class wrapperClass;
  17. if (wrapperClasses != null && !wrapperClasses.isEmpty()) {
  18. // 将当前instance作为参数传给Wrapper的构造方法,并通过反射创建Wrapper实例。
  19. // 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
  20. //这里实际上是AOP的实现
  21. for( Iterator i$ = wrapperClasses.iterator(); i$.hasNext(); instance = this.injectExtension(wrapperClass.getConstructor( this.type).newInstance(instance))) {
  22. // 通过含参的构造方法将SPI实例(根据指定名字创建好的)注入进去
  23. // 注入成功并创建好实例之后会把这个组装好的Wrapper实例返回
  24. // 这样循环到下一个Wrapper类时其实注入的是上一个Wrapper类实例
  25. // 这也解释了为什么后定义的先执行
  26. wrapperClass = (Class)i$.next();
  27. }
  28. }
  29. return instance;
  30. } catch (Throwable var7) {
  31. throw new IllegalStateException( "Extension instance(name: " + name + ", class: " + this.type + ") could not be instantiated: " + var7.getMessage(), var7);
  32. }
  33. }
  34. }
  35. //对SPI注解进行解析,从配置文件的目录加载扩展类
  36. private Map<String, Class<?>> loadExtensionClasses() {
  37. SPI defaultAnnotation = (SPI) this.type.getAnnotation(SPI.class);
  38. if (defaultAnnotation != null) {
  39. String value = defaultAnnotation.value();
  40. if ((value = value.trim()).length() > 0) {
  41. String[] names = NAME_SEPARATOR.split(value);
  42. if (names.length > 1) {
  43. throw new IllegalStateException( "more than 1 default extension name on extension " + this.type.getName() + ": " + Arrays.toString(names));
  44. }
  45. if (names.length == 1) {
  46. this.cachedDefaultName = names[ 0];
  47. }
  48. }
  49. }
  50. Map<String, Class<?>> extensionClasses = new HashMap();
  51. this.loadDirectory(extensionClasses, "META-INF/dubbo/internal/");
  52. this.loadDirectory(extensionClasses, "META-INF/dubbo/");
  53. this.loadDirectory(extensionClasses, "META-INF/services/");
  54. return extensionClasses;
  55. }

这里的IOC是基于setter函数注入依赖来实现


   
   
  1. private T injectExtension (T instance) {
  2. try {
  3. if ( this.objectFactory != null) {
  4. Method[] arr$ = instance.getClass().getMethods();
  5. int len$ = arr$.length;
  6. for( int i$ = 0; i$ < len$; ++i$) {
  7. Method method = arr$[i$];
  8. if (method.getName().startsWith( "set") && method.getParameterTypes().length == 1 && Modifier.isPublic(method.getModifiers())) {
  9. //获取setter方法参数类型
  10. Class pt = method.getParameterTypes()[ 0];
  11. try {
  12. String property = method.getName().length() > 3 ? method.getName().substring( 3, 4).toLowerCase() + method.getName().substring( 4) : "";
  13. // 从 ObjectFactory 中获取依赖对象
  14. Object object = this.objectFactory.getExtension(pt, property);
  15. if (object != null) {
  16. // 通过反射调用 setter 方法设置依赖
  17. method.invoke(instance, object);
  18. }
  19. } catch (Exception var9) {
  20. logger.error( "fail to inject via method " + method.getName() + " of interface " + this.type.getName() + ": " + var9.getMessage(), var9);
  21. }
  22. }
  23. }
  24. }
  25. } catch (Exception var10) {
  26. logger.error(var10.getMessage(), var10);
  27. }
  28. return instance;
  29. }

4.聊聊AOP的实现

参考博客

在上面的源码实现中,涉及到wapper类的处理,dubbo正是基于wapper类来实现wapper,其判断一个类是否为wapper,其实就是判断该类是否含有一个参数类型为SPI接口类型的构造函数


   
   
  1. private boolean isWrapperClass (Class<?> clazz) {
  2. try {
  3. // 尝试取得参数类型为SPI接口类型的构造函数
  4. clazz.getConstructor( this.type);
  5. return true;
  6. } catch (NoSuchMethodException var3) {
  7. return false;
  8. }
  9. }

同时,在上面的循环部分代码中,可以看出当存在多个wapper时,每个wapper被注入的instance对象实际上是上一个wapper。下面通过一个示例来进行演示

  • 同样创建一个接口类AopService


   
   
  1. @SPI
  2. public interface AopService {
  3. @Adaptive
  4. void service ();
  5. }
  • 创建两个实现类


   
   
  1. public class CppAopService implements AopService {
  2. @Override
  3. public void service () {
  4. System.out.println( "this is c++ aop service");
  5. }
  6. }
  7. public class JavaAopService implements AopService {
  8. @Override
  9. public void service () {
  10. System.out.println( "this is java aop service");
  11. }
  12. }
  • 创建wapper类,用来增强实现类(AOP)


   
   
  1. public class AopServiceWapper1 implements AopService {
  2. private AopService aopService;
  3. //必须有这个构造方法才能被判断为wapper类
  4. public AopServiceWapper1 (AopService service) {
  5. this.aopService = service;
  6. }
  7. @Override
  8. public void service () {
  9. System.out.println( "before wapper1");
  10. aopService.service();
  11. System.out.println( "after wapper1");
  12. }
  13. }
  14. public class AopServiceWapper2 implements AopService {
  15. private AopService aopService;
  16. //必须有这个构造方法才能被判断为wapper类
  17. public AopServiceWapper2 (AopService service) {
  18. this.aopService = service;
  19. }
  20. @Override
  21. public void service () {
  22. System.out.println( "before wapper2");
  23. aopService.service();
  24. System.out.println( "after wapper2");
  25. }
  26. }
  • 在配置文件中需要加入两个wapper


   
   
  1. wapper1=com.lzq.dubboaopdemo.aopservice.Impl.AopServiceWapper1
  2. wapper2=com.lzq.dubboaopdemo.aopservice.Impl.AopServiceWapper2
  3. java=com.lzq.dubboaopdemo.aopservice.Impl.JavaAopService
  4. cpp=com.lzq.dubboaopdemo.aopservice.Impl.JavaAopService
  • 进行测试


   
   
  1. public static void main (String[] args) {
  2. ExtensionLoader<AopService> loader = ExtensionLoader.getExtensionLoader(AopService.class);
  3. AopService service = loader.getExtension( "java");
  4. service.service();
  5. }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值