dubbo的SPI 机制与运用实现

政采云技术 发表于 11月以前  | 总阅读数:518 次

前言

SPI(Service Provider Interface),是 JDK 内置的一种服务发现机制,可以用来启用框架扩展和替换组件,主要被开发人员使用。比如 java.sql.Driver 接口,不同厂商可以针对同一接口提供不同实现,MySQL 和 PostgreSQL 都有各自不同实现提供给用户。Java SPI 机制可以为某个接口寻找服务实现。Java 中 SPI 机制主要思想是将装配控制权移到程序之外,模块化设计中这个机制尤其重要,其核心思想就是 解耦

其流程可以参考下图

Dubbo 并未使用 Java 原生 SPI 机制,而是对其进行增强,能够更好满足需求。

在学习 Dubbo SPI 之前,需要先了解 Java SPI 运行原理。

Java SPI

SPI 简单示例

  1. 这是一个搜索接口(行为)
public interface Search {
  List<String> searchData(String query);
}
  • 文件搜索实现
public class FileSearch implements Search {
  @Override
  public List<String> searchData(String query) {
    return Lists.newArrayList("这里是文件搜索");
  }
}
  • DB 搜索实现
public class DbSearch implements Search {
  @Override
  public List<String> searchData(String query) {
    return Lists.newArrayList("这里 db 搜索");
  }
}
  • ES 搜索实现
public class ElasticSearchSearch implements Search {
  @Override
  public List<String> searchData(String query) {
    return Lists.newArrayList("这里是 es 搜索");
  }
}
  1. 在resources下新建 META-INF/services/ 目录,新建接口全限定名的文件: cn.zcy.spi.Search,文件内加上需要用到的实现类
cn.zcy.spi.DbSearch
cn.zcy.spi.FileSearch
cn.zcy.spi.ElasticSearchSearch
  1. 测试
public class start {

  public static void main(String[] args) {
    ServiceLoader<Search> s = ServiceLoader.load(Search.class);
    Iterator<Search> iterator = s.iterator();
    while (iterator.hasNext()) {
      Search next = iterator.next();
        System.out.println(next.searchData());
    }
  }
}

结果:Main 方法会将文件内部定义的所有实现类实例化,并调用其 SearchData 方法。

[这里是 db 搜索]
[这里是文件搜索]
[这里是 es 搜索]

这就是 SPI 思想,接口实现由 Provider 提供,Provider 只用在提供的 Jar 包 META-INF/services 下根据平台定义的接口新建文件,并添加进相应的实现类内容就好。

SPI 实现

通过上述例子,可以简单猜想一下 ServiceLoader.Load 的实现方式:

  1. META-INF/services 目录下创建的文件名,是需要 Load 方法加载的接口的全限定名, 即系统可以通过 Load 方法入参, 找到定义的文件。
  2. 通过读取文件内容找到对应实现类的全限定名。
  3. 知道全限定名后, 很容易通过反射的方式来拿到对应的实现, 然后执行对应的方法。

根据以上猜想看 ServiceLoader.Load 具体实现

省略部分代码, 只关注关键处理逻辑

//ServiceLoader实现了Iterable接口,可以遍历所有的服务实现者
public final class ServiceLoader<S> implements Iterable<S>{
  //查找配置文件的目录
  private static final String PREFIX = "META-INF/services/";

  //解析服务提供者配置文件中的一行
  //首先去掉注释校验,然后保存
  //返回下一行行号
  //重复的配置项和已经被实例化的配置项不会被保存
  private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, List<String> names) throws IOException, ServiceConfigurationError {
        //读取一行
    String ln = r.readLine();
    if (ln == null) {
        return -1;
    }
    //#号代表注释行
    int ci = ln.indexOf('#');
    if (ci >= 0) ln = ln.substring(0, ci);
    ln = ln.trim();
    int n = ln.length();
    if (n != 0) {
        if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
           fail(service, u, lc, "Illegal configuration-file syntax");
        int cp = ln.codePointAt(0);
        if (!Character.isJavaIdentifierStart(cp))
           fail(service, u, lc, "Illegal provider-class name: " + ln);
        for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
            cp = ln.codePointAt(i);
            if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
              fail(service, u, lc, "Illegal provider-class name: " + ln);
        }
        if (!providers.containsKey(ln) && !names.contains(ln))
          names.add(ln);
    }
    return lc + 1;
}

//解析配置文件,解析指定的url配置文件
//使用parseLine方法进行解析,未被实例化的服务提供者会被保存到缓存中去
private Iterator<String> parse(Class<?> service, URL u) throws ServiceConfigurationError {
  InputStream in = null;
  BufferedReader r = null;
  ArrayList<String> names = new ArrayList<>();
  try {
    in = u.openStream();
    r = new BufferedReader(new InputStreamReader(in, "utf-8"));
    int lc = 1;
    while ((lc = parseLine(service, u, r, lc, names)) >= 0);
  }
  return names.iterator();
}

//服务提供者查找的迭代器
private class LazyIterator implements Iterator<S>{

  Class<S> service;//服务提供者接口
  ClassLoader loader;//类加载器
  Enumeration<URL> configs = null;//保存实现类的url
  Iterator<String> pending = null;//保存实现类的全名
  String nextName = null;//迭代器中下一个实现类的全名

  private LazyIterator(Class<S> service, ClassLoader loader) {
    this.service = service;
    this.loader = loader;
  }

  private boolean hasNextService() {
    if (nextName != null) {
      return true;
    }
    if (configs == null) {
      try {
        String fullName = PREFIX + service.getName();
        if (loader == null)
          configs = ClassLoader.getSystemResources(fullName);
        else
          configs = loader.getResources(fullName);
        }
      }
      while ((pending == null) || !pending.hasNext()) {
        if (!configs.hasMoreElements()) {
          return false;
        }
        pending = parse(service, configs.nextElement());
       }
      nextName = pending.next();
      return true;
  }

  private S nextService() {
    if (!hasNextService())
      throw new NoSuchElementException();
      String cn = nextName;
      nextName = null;
      Class<?> c = null;
      try {
        c = Class.forName(cn, false, loader);
      }
      if (!service.isAssignableFrom(c)) {
        fail(service, "Provider " + cn  + " not a subtype");
      }
      try {
        S p = service.cast(c.newInstance());
        providers.put(cn, p);
        return p;
      }
   }

  public boolean hasNext() {
    if (acc == null) {
      return hasNextService();
    } else {
      PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
      public Boolean run() { return hasNextService(); }
      };
      return AccessController.doPrivileged(action, acc);
      }
    }

  public S next() {
     if (acc == null) {
       return nextService();
     } else {
       PrivilegedAction<S> action = new PrivilegedAction<S>() {
         public S run() { return nextService(); }
         };
       return AccessController.doPrivileged(action, acc);
      }
  }

  public void remove() {
    throw new UnsupportedOperationException();
  }
}

//获取迭代器
//返回遍历服务提供者的迭代器
//以懒加载的方式加载可用的服务提供者
//懒加载的实现是:解析配置文件和实例化服务提供者的工作由迭代器本身完成
public Iterator<S> iterator() {
  return new Iterator<S>() {
    //按照实例化顺序返回已经缓存的服务提供者实例
    Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
    public boolean hasNext() {
      if (knownProviders.hasNext())
        return true;
      return lookupIterator.hasNext();
    }

   public S next() {
      if (knownProviders.hasNext())
        return knownProviders.next().getValue();
      return lookupIterator.next();
     }

   public void remove() {
     throw new UnsupportedOperationException();
   }
  };
}

  //使用扩展类加载器为指定的服务创建ServiceLoader
  //只能找到并加载已经安装到当前Java虚拟机中的服务提供者,应用程序类路径中的服务提供者将被忽略
  public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
    ClassLoader cl = ClassLoader.getSystemClassLoader();
    ClassLoader prev = null;
    while (cl != null) {
      prev = cl;
      cl = cl.getParent();
    }
    return ServiceLoader.load(service, prev);
  }
}
  1. 首先ServiceLoader 实现 Iterable 接口,所以具有迭代器属性,主要实现了迭代器的 HasNextNext 方法,调用 LookupIteratorHasNextNext 方法,LookupIterator 是懒加载迭代器。
  2. 其次LazyIteratorHasNext 方法,静态变量 Prefix 就是 META-INF/services/ 目录,这就是为什么需要在 ClasspathMETA-INF/services/目录创建以服务接口命名的文件。
  3. 最后 ,通过反射方法 Class.ForName()加载类对象,并用 NewInstance 方法将类实例化,并把实例化后的类缓存到 Providers对象中,(LinkedHashMap<String,S>类型)然后返回实例对象。

所以可以看到 ServiceLoader 不是实例化后就读取配置文件中具体实现并进行实例化,而是等到使用迭代器去遍历的时候,才会加载对应配置文件去解析,调用 HasNext 方法的时候会去加载配置文件进行解析,调用 Next 方法的时候进行实例化并缓存。

所有配置文件只会加载一次,服务提供者也只会被实例化一次,重新加载配置文件可使用 Reload 方法。

SPI 机制缺陷

通过阅读 ServiceLoader 源码,可以反向推断出基础 SPI 在使用上存在部分缺陷:

  1. 无法按需来获取。遍历所有实现,并实例化,然后在循环中才能找到需要的实现。如果不想用某些实现类,或者某些类实例化很耗时,也会被载入并实例化了,造成了浪费。
  2. 获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。
  3. 多个并发线程使用 ServiceLoader 类的实例不安全。

Dubbo SPI 机制

Dubbo SPI 并非原创一种新的加载机制,它是在 Java 原生 SPI 基础上加以改进,解决原生 SPI 一些缺陷。

Dubbo SPI 机制简单示例

  1. 同样 Search 接口
@SPI
public interface Search {
  List<String> searchData();
}
  • 文件搜索实现
public class FileSearch implements Search {
  @Override
  public List<String> searchData() {
    return Lists.newArrayList("这里是文件搜索");
  }
}
  • DB 搜索实现
public class DbSearch implements Search {
  @Override
  public List<String> searchData() {
    return Lists.newArrayList("这里是 db 搜索");
  }
}
  • ES 搜索实现
public class ElasticSearchSearch implements Search {
  @Override
  public List<String> searchData() {
    return Lists.newArrayList("这里是 es 搜索");
  }
}
  1. 接下来同样是在 META-INF/services/ 目录下,新建接口全限定名的文件: cn.zcy.spi.Search,里面加上我们需要用到的实现类
db=cn.lzy.playwright.spi.dubbo.DbSearch
file=cn.lzy.playwright.spi.dubbo.FileSearch
es=cn.lzy.playwright.spi.dubbo.ElasticSearchSearch
  1. 测试
@Slf4j
public class start {

  public static void main(String[] args) {
    ExtensionLoader<Search> extensionLoader = ExtensionLoader.getExtensionLoader(Search.class);
    Search dbSearch = extensionLoader.getExtension("db");
    log.info("dbSearch.method : {}", dbSearch.searchData());
    Search fileSearch = extensionLoader.getExtension("file");
    log.info("fileSearch.method : {}", fileSearch.searchData());
    Search esSearch = extensionLoader.getExtension("es");
    log.info("esSearch.method : {}", esSearch.searchData());
  }
}

结果

dbSearch.method : [这里是 db 搜索]
fileSearch.method : [这里是文件搜索]
esSearch.method : [这里是 es 搜索]

分析一下,Dubbo SPI 和 Java 原生 SPI 实现上,有三个明显不同:

  • Interface 上加入了 Dubbo 内部 @SPI 。
  • 全限定名文件的内容,不再仅仅只是对应实现的全限定名, 而是类似 K-V 格式一种编写方式。
  • Main 方法内部,不是通过原生 ServiceLoader 加载,而是通过 Dubbo 自定义的 ExtensionLoader 加载。

通过示例很明显发现:Dubbo SPI 机制,通过改变全限定名文件内容格式,解决了原生 SPI 在使用上无法按需实例化这个缺陷。

那么 Dubbo SPI 如何实现,接下来看一下对应源码。

Dubbo SPI 实现

从入口 ExtensionLoader 来查看 GetExtensinLoader 方法

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
  if (type == null) { // 规避了入参 NPE 问题
    throw new IllegalArgumentException("Extension type == null");
  }
  if (!type.isInterface()) { // 规避入参非接口的问题
    throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
  }
  if (!withExtensionAnnotation(type)) { // 方法内部判读接口是否存在 @SPI 注解
    throw new IllegalArgumentException("Extension type(" + type +
        ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!");
  }

  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;
  }

方法内容很简单,除去一些判断外, 真正有价值的逻辑处理只有一个懒加载的实现,而关于代码上中 EXTENSION_LOADERS 其实是一个 ConcurrentHashMap

private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<Class<?>, ExtensionLoader<?>>();

这在一定程度上解决了重复读取和并发问题。关键读取处理逻辑是在 new ExtensionLoader<T>(type) 这个构造方法内部,这个构造方法十分简单,是一个三元表达式

private ExtensionLoader(Class<?> type) {
  this.type = type;
  objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension());
}

所以要关注的就是 GetAdaptiveExtension 这个方法实现

如下是具体实现逻辑

public T getAdaptiveExtension() {
  Object instance = cachedAdaptiveInstance.get();
  if (instance == null) {
  if (createAdaptiveInstanceError == null) {
    synchronized (cachedAdaptiveInstance) { // 懒加载的双重锁校验, 避免了并发问题
      instance = cachedAdaptiveInstance.get();
      if (instance == null) {
        try {
          instance = createAdaptiveExtension(); // 实例化的关键处理
          cachedAdaptiveInstance.set(instance);
        } catch (Throwable t) {
          createAdaptiveInstanceError = t;
          throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t);
        }
      }
    }
  } else {
    throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError);
    }
  }
  return (T) instance;
}

CreateAdaptiveExtension 关键是调用 GetAdaptiveExtensionClass 方法

private T createAdaptiveExtension() {
  try {
    return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
    throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e);
    }
 }

继续看内部逻辑, 查到 GetExtensionClasses 方法

private Class<?> getAdaptiveExtensionClass() {
  getExtensionClasses();
  if (cachedAdaptiveClass != null) {
    return cachedAdaptiveClass;
  }
  return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

最后看到读取 Interface 对应实现类的方法是 LoadExtensionClasses , 具体实现逻辑如下

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;
}

 // synchronized in getExtensionClasses
private Map<String, Class<?>> loadExtensionClasses() {
   final SPI defaultAnnotation = type.getAnnotation(SPI.class);
   if (defaultAnnotation != null) {
     String value = defaultAnnotation.value();
     if ((value = value.trim()).length() > 0) {
       String[] names = NAME_SEPARATOR.split(value);
       if (names.length > 1) {
         throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(names));
        }
        if (names.length == 1) {
          cachedDefaultName = names[0];
        }
      }
  }
  Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>();
  // 指定地址读取文件内容, 兼容原生的 SPI 地址和默认的 duboo 文件地址
  loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName());
  loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
  loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName());
  loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
  loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName());
  loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba"));
        return extensionClasses;
}

private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {
  String fileName = dir + type;
  try {
    Enumeration<java.net.URL> urls;
    ClassLoader classLoader = findClassLoader();
    if (classLoader != null) {
      urls = classLoader.getResources(fileName);
    } else {
      urls = ClassLoader.getSystemResources(fileName);
    }
    if (urls != null) {
      while (urls.hasMoreElements()) {
        java.net.URL resourceURL = urls.nextElement();
        loadResource(extensionClasses, classLoader, resourceURL); // 读取指定地址文件资源
       }
    }
  } catch (Throwable t) {
    logger.error("Exception when load extension class(interface: " +type + ", description file: " + fileName + ").", t);
  }
}

private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) {
  try {
    BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8"));
    try {
      String line;
      // 按行读取配置
      while ((line = reader.readLine()) != null) {
        final int ci = line.indexOf('#');
        if (ci >= 0) {
        // 忽略 # 后的注释内容
        line = line.substring(0, ci);
      }
      line = line.trim();
      if (line.length() > 0) {
        try {
          String name = null;
          // 根据 = 分割内容, 拆解成 K-V 格式数据
          int i = line.indexOf('=');
          if (i > 0) {
            name = line.substring(0, i).trim();
            line = line.substring(i + 1).trim();
          }
          if (line.length() > 0) {
            // 加载 Class
            loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name);
          }
        } catch (Throwable t) {
          IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t);
                            exceptions.put(line, e);
        }
      }
    }
  } finally {
    reader.close();
  }
  } catch (Throwable t) {
    logger.error("Exception when load extension class(interface: " +type + ", class file: " + resourceURL + ") in " + resourceURL, t);
  }
}

由上述代码逻辑可以了解,Dubbo SPI 加载 Class 信息过程主要分成以下几步:

  1. 首先:通过名称获取拓展类之前,需要根据配置文件解析出拓展项名称到拓展类的映射关系表(Map<名称, 拓展类>),之后再根据拓展项名称从映射关系表中取出相应的拓展类
  2. 其次GetExtensionClasses 内部先检查缓存,若缓存未命中,则通过 synchronized 加锁。加锁后再次检查缓存,并判空,此时如果 Classes 仍为 Null,则通过 LoadExtensionClasses 加载拓展类
  3. 然后LoadExtensionClasses 中的逻辑比较简单, 总共就两件事情:一是对 SPI 注解进行解析,二是调用 LoadDirectory 方法加载指定文件夹配置文件
  4. 接着LoadDirectory 方法先通过ClassLoader 获取所有资源链接,然后再通过 LoadResource 方法加载资源
  5. 最后LoadResource 方法用于读取和解析配置文件,并通过反射加载类,最后调用 LoadClass 方法进行其他操作。LoadClass 方法用于主要用于操作缓存

加载完 Interface 对应实现 Class 之后就需要对其进行实例化,看一下源码实现

public T getExtension(String name) {
  if (name == null || name.length() == 0) {
    throw new IllegalArgumentException("Extension name == null");
  }
  if ("true".equals(name)) {
     return getDefaultExtension();
  }
  // 缓存 Holder
  Holder<Object> holder = cachedInstances.get(name);
  if (holder == null) {
     cachedInstances.putIfAbsent(name, new Holder<Object>());
     holder = cachedInstances.get(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;
 }

该方法内部是一个普通缓存读取内容,创建内容在方法 CreateExtension 内部

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 (wrapperClasses != null && !wrapperClasses.isEmpty()) {
        // 循环创建 Wrapper 实例
        for (Class<?> wrapperClass : wrapperClasses) {
          // 将当前 instance 作为参数传给 Wrapper 的构造方法,并通过反射创建 Wrapper 实例。
          // 然后向 Wrapper 实例中注入依赖,最后将 Wrapper 实例再次赋值给 instance 变量
          instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance));
        }
      }
      return instance;
    } catch (Throwable t) {
       throw new IllegalStateException("Extension instance(name: " + name + ", class: " +
                    type + ")  could not be instantiated: " + t.getMessage(), t);
   }
 }

CreateExtension 方法逻辑稍复杂一下,包含了如下步骤:

  • 通过 GetExtensionClasses 获取所有的拓展类
  • 通过反射创建拓展对象
  • 向拓展对象中注入依赖
  • 将拓展对象包裹在相应的 Wrapper 对象中

以上步骤中,第一个步骤是加载拓展类的关键,第三和第四个步骤是 Dubbo IOC 与 AOP 的具体实现

private T injectExtension(T instance) {
  try {
    if (objectFactory != null) {
      // 遍历目标类的所有方法
      for (Method method : instance.getClass().getMethods()) {
        // 检测方法是否以 set 开头,且方法仅有一个参数,且方法访问级别为 public
        if (method.getName().startsWith("set") && method.getParameterTypes().length == 1
          && Modifier.isPublic(method.getModifiers())) {
          /**
          * Check {@link DisableInject} to see if we need auto injection for this property
          */
          if (method.getAnnotation(DisableInject.class) != null) {
            continue;
          }
          // 获取 setter 方法参数类型
          Class<?> pt = method.getParameterTypes()[0];
          if (ReflectUtils.isPrimitives(pt)) {
            continue;
          }
          try {
            // 获取属性名,比如 setName 方法对应属性名 name
            String property = method.getName().length() > 3 ? method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4) : "";
           // 从 ObjectFactory 中获取依赖对象
           Object object = objectFactory.getExtension(pt, property);
           if (object != null) {
             // 通过反射调用 setter 方法设置依赖
             method.invoke(instance, object);
           }
         } catch (Exception e) {
           logger.error("fail to inject via method " + method.getName()
                                    + " of interface " + type.getName() + ": " + e.getMessage(), e);
         }
       }
     }
   }
 } catch (Exception e) {
   logger.error(e.getMessage(), e);
 }
 return instance;
}

在上面代码中,ObjectFactory 变量类型为 AdaptiveExtensionFactoryAdaptiveExtensionFactory 内部维护了一个 ExtensionFactory 列表,用于存储其他类型的 ExtensionFactory。Dubbo 目前提供两种 ExtensionFactory,分别是 SpiExtensionFactorySpringExtensionFactory。前者用于创建自适应的拓展,后者用于从 Spring IOC 容器中获取所需拓展。这两个类代码不是很复杂,这里就不一一分析了。

可以看出,Dubbo SPI 在原生 SPI 基础上,通过对配置文件格式改造,生成了一个映射关系表(Map<名称, 拓展类>),这样能够按需去实例化需要的实现类。解决原生 SPI 必须全部遍历然后遍历查询实现实例的问题.

同时无论是读取配置 Class 亦或是实例化对应的实现实例,都采用了懒加载的双重锁校验,解决了原生 SPI 在多线程情况下,可能存在的并发问题。

总结

自 JDK 1.6 以后,Java 提出了 SPI,一种服务提供发现机制,可以用来启用框架扩展和替换组件。其主要目的是将装配控制权移到程序之外,模块化设计中这个机制尤其重要。其核心思想是 解耦

这种原生 SPI 机制并不完美,其实现存在若干问题

  • 无法按需进行实例化,只能全部实例化之后,遍历去获取需要的实现,造成内存浪费以及无谓的实例化资源消耗。
  • 并没有解决多线程下的并发问题。

Duboo 在原生 SPI 基础上进行改良,通过改变配置文件内容格式,针对每个实现类划分出 K-V ,能够满足在业务上按需实例化的要求。同时采用懒加载的双重锁校验,解决多线程情况下可能存在的并发问题。

本文由微信公众号政采云技术原创,哈喽比特收录。
文章来源:https://mp.weixin.qq.com/s/sDTzyvWE0nDpaBixFv92Kg

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 目录