dubbo之SPI

1、SPI简介

SPI 全称为 Service Provider Interface,是一种服务发现机制。SPI 的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。SPI 机制在第三方框架中也有所应用,比如 Dubbo 就是通过 SPI 机制加载所有的组件。不过,Dubbo 并未使用 Java 原生的 SPI 机制,而是对其进行了增强,使其能够更好的满足需求。在 Dubbo 中,SPI 是一个非常重要的模块。基于 SPI,我们可以很容易的对 Dubbo 进行拓展。如果大家想要学习 Dubbo 的源码,SPI 机制务必弄懂。接下来,我们先来了解一下 Java SPI 与 Dubbo SPI 的用法,然后再来分析 Dubbo SPI 的源码。

2、SPI示例

2.1 Java SPI示例

本节通过一个示例演示 Java SPI 的使用方法。首先,我们定义一个接口,名称为 HelloService。

public interface HelloService {     void sayHello(); }

家下来定义两个实现类:HelloAService、HelloBService;

public class HelloAService implements HelloService {          @Override     public void sayHello() {         System.out.println("Hello, I am A");     } }  public class HelloBService implements HelloService {      @Override     public void sayHello() {         System.out.println("Hello, I am B");     } }

接下来 META-INF/services 文件夹下创建一个文件,名称为 Robot 的全限定名 org.apache.spi.Robot。文件内容为实现类的全限定的类名,如下:

org.apache.spi.HelloAService org.apache.spi.HelloBService

做好所需的准备工作,接下来编写代码进行测试

public class JavaSPITest {      @Test     public void sayHello() throws Exception {         ServiceLoader<HelloService> serviceLoader = ServiceLoader.load(HelloService.class);         System.out.println("Java SPI");         serviceLoader.forEach(HelloService::sayHello);     } }

最后结果如下:

Java SPI Hello, I am A Hello, I am B

从测试结果可以看出,我们的两个实现类被成功的加载,并输出了相应的内容。关于 Java SPI 的演示先到这里,接下来演示 Dubbo SPI。

2.2 Dubbo SPI

Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,配置内容如下。

helloAService = org.apache.spi.HelloAService helloBService = org.apache.spi.HelloBService

与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外,在测试 Dubbo SPI 时,需要在 Robot 接口上标注 @SPI 注解。下面来演示 Dubbo SPI 的用法:

public class DubboSPITest {      @Test     public void sayHello() throws Exception {         ExtensionLoader<HelloService> extensionLoader =              ExtensionLoader.getExtensionLoader(HelloService.class);         HelloService a = extensionLoader.getExtension("HelloAService");         a.sayHello();         HelloService b = extensionLoader.getExtension("HelloBService");         b.sayHello();     } }

测试结果如下:

Java SPI Hello, I am A Hello, I am B

3、Dubbo SPI源码解析

Dubbo SPI相关逻辑都在ExtensionLoader 类中,首先通过getExtensionLoader获取一个ExtensionLoader实例,然后在根据getExtension获取type的扩展类。
getExtensionLoader方法比较简单,先从缓存中获取,如果缓存不存在,则创建ExtensionLoader对象,并存入缓存中,在看getExtension方法:

    public T getExtension(String name) {         if (StringUtils.isEmpty(name)) {             throw new IllegalArgumentException("Extension name == null");         }         if ("true".equals(name)) {             return getDefaultExtension();         }         // 根据扩展名从缓存中获取,缓存中没有,创建并存入缓存         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;     }

上面代码的逻辑比较简单,首先检查缓存,缓存未命中则创建拓展对象。下面我们来看一下创建拓展对象的过程是怎样的。

    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 作为参数传给 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);         }     }

我们在通过名称获取拓展类之前,首先需要根据配置文件解析出拓展项名称到拓展类的映射关系表(Map<名称, 拓展类>),之后再根据拓展项名称从映射关系表中取出相应的拓展类即可。相关过程的代码分析如下

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

getExtensionClasses方法同样是先从缓存中读取,缓存不存在,在去加载:

    private Map<String, Class<?>> loadExtensionClasses() {         // 获取扩展类SPI注解         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<?>>();         // 加载指定文件夹下的配置文件         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;     }

loadExtensionClasses 方法总共做了两件事情,一是对 SPI 注解进行解析,二是调用 loadDirectory 方法加载指定文件夹配置文件。SPI 注解解析过程比较简单,无需多说。下面我们来看一下 loadDirectory 做了哪些事情。

    private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir, String type) {         // fileName = 文件夹路径 + 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);         }     }

loadDirectory 方法先通过 classLoader 获取所有资源链接,然后再通过 loadResource 方法加载资源。我们继续跟下去,看一下 loadResource 方法的实现。

    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;                             int i = line.indexOf('=');                             if (i > 0) {                                 // 以等于号 = 为界,截取键与值                                 name = line.substring(0, i).trim();                                 line = line.substring(i + 1).trim();                             }                             if (line.length() > 0) {                                 // 加载类,并通过 loadClass 方法对类进行缓存                                 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);         }     }

loadClass()方法主要是利用反射原理,根据类的权限定名加载成类,并存入缓存中

脚本宝典为你提供优质服务
脚本宝典 » dubbo之SPI

发表评论

提供最优质的资源集合

立即查看 了解详情