结合实战和源码来聊聊Java中的SPI机制?

开发 后端
SPI机制能够非常方便的为某个接口动态指定其实现类,在某种程度上,这也是某些框架具有高度可扩展性的基础。今天,我们就从源码级别深入探讨下Java中的SPI机制。

[[353170]]

作者个人研发的在高并发场景下,提供的简单、稳定、可扩展的延迟消息队列框架,具有精准的定时任务和延迟队列处理功能。自开源半年多以来,已成功为十几家中小型企业提供了精准定时调度方案,经受住了生产环境的考验。为使更多童鞋受益,现给出开源框架地址:https://github.com/sunshinelyz/mykit-delay

写在前面

SPI机制能够非常方便的为某个接口动态指定其实现类,在某种程度上,这也是某些框架具有高度可扩展性的基础。今天,我们就从源码级别深入探讨下Java中的SPI机制。

注:文章已收录到:https://github.com/sunshinelyz/technology-binghe

SPI的概念

SPI在Java中的全称为Service Provider Interface,是JDK内置的一种服务提供发现机制,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。

  1. JAVA SPI = 基于接口的编程+策略模式+配置文件的动态加载机制 

SPI的使用场景

Java是一种面向对象语言,虽然Java8开始支持函数式编程和Stream,但是总体来说,还是面向对象的语言。在使用Java进行面向对象开发时,一般会推荐使用基于接口的编程,程序的模块与模块之前不会直接进行实现类的硬编码。而在实际的开发过程中,往往一个接口会有多个实现类,各实现类要么实现的逻辑不同,要么使用的方式不同,还有的就是实现的技术不同。为了使调用方在调用接口的时候,明确的知道自己调用的是接口的哪个实现类,或者说为了实现在模块装配的时候不用在程序里动态指明,这就需要一种服务发现机制。Java中的SPI加载机制能够满足这样的需求,它能够自动寻找某个接口的实现类。

大量的框架使用了Java的SPI技术,如下:

(1)JDBC加载不同类型的数据库驱动 (2)日志门面接口实现类加载,SLF4J加载不同提供商的日志实现类 (3)Spring中大量使用了SPI

对servlet3.0规范

对ServletContainerInitializer的实现

自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)等

(4)Dubbo里面有很多个组件,每个组件在框架中都是以接口的形成抽象出来!具体的实现又分很多种,在程序执行时根据用户的配置来按需取接口的实现

SPI的使用

当服务的提供者,提供了接口的一种实现后,需要在Jar包的META-INF/services/目录下,创建一个以接口的名称(包名.接口名的形式)命名的文件,在文件中配置接口的实现类(完整的包名+类名)。

当外部程序通过java.util.ServiceLoader类装载这个接口时,就能够通过该Jar包的META/Services/目录里的配置文件找到具体的实现类名,装载实例化,完成注入。同时,SPI的规范规定了接口的实现类必须有一个无参构造方法。

SPI中查找接口的实现类是通过java.util.ServiceLoader,而在java.util.ServiceLoader类中有一行代码如下:

  1. // 加载具体实现类信息的前缀,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目录下 
  2. private static final String PREFIX = "META-INF/services/"

这也就是说,我们必须将接口的配置文件写到Jar包的META/Services/目录下。

SPI实例

这里,给出一个简单的SPI使用实例,演示在Java程序中如何使用SPI动态加载接口的实现类。

注意:实例是基于Java8进行开发的。

1.创建Maven项目

在IDEA中创建Maven项目spi-demo,如下:

2.编辑pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?> 
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" 
  3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  4.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
  5.  
  6. <artifactId>spi-demo</artifactId> 
  7. <groupId>io.binghe.spi</groupId> 
  8. <packaging>jar</packaging> 
  9. <version>1.0.0-SNAPSHOT</version> 
  10. <modelVersion>4.0.0</modelVersion> 
  11.  
  12. <build> 
  13.     <plugins> 
  14.         <plugin> 
  15.             <groupId>org.springframework.boot</groupId> 
  16.             <artifactId>spring-boot-maven-plugin</artifactId> 
  17.         </plugin> 
  18.         <plugin> 
  19.             <groupId>org.apache.maven.plugins</groupId> 
  20.             <artifactId>maven-compiler-plugin</artifactId> 
  21.             <version>3.6.0</version> 
  22.             <configuration> 
  23.                 <source>1.8</source> 
  24.                 <target>1.8</target> 
  25.             </configuration> 
  26.         </plugin> 
  27.     </plugins> 
  28. </build> 
  29.  
  30. </project> 

3.创建类加载工具类

在io.binghe.spi.loader包下创建MyServiceLoader,MyServiceLoader类中直接调用JDK的ServiceLoader类加载Class。代码如下所示。

  1. package io.binghe.spi.loader; 
  2.   
  3. import java.util.ServiceLoader; 
  4.   
  5. /** 
  6.  * @author binghe 
  7.  * @version 1.0.0 
  8.  * @description 类加载工具 
  9.  */ 
  10. public class MyServiceLoader { 
  11.   
  12.     /** 
  13.      * 使用SPI机制加载所有的Class 
  14.      */ 
  15.     public static <S> ServiceLoader<S> loadAll(final Class<S> clazz) { 
  16.         return ServiceLoader.load(clazz); 
  17.     } 

4.创建接口

在io.binghe.spi.service包下创建接口MyService,作为测试接口,接口中只有一个方法,打印传入的字符串信息。代码如下所示:

  1. package io.binghe.spi.service; 
  2.   
  3. /** 
  4.  * @author binghe 
  5.  * @version 1.0.0 
  6.  * @description 定义接口 
  7.  */ 
  8. public interface MyService { 
  9.   
  10.     /** 
  11.      *  打印信息 
  12.      */ 
  13.     void print(String info); 

5.创建接口的实现类

(1)创建第一个实现类MyServiceA

在io.binghe.spi.service.impl包下创建MyServiceA类,实现MyService接口。代码如下所示:

  1. package io.binghe.spi.service.impl; 
  2. import io.binghe.spi.service.MyService; 
  3.   
  4. /** 
  5.  * @author binghe 
  6.  * @version 1.0.0 
  7.  * @description 接口的第一个实现 
  8.  */ 
  9. public class MyServiceA implements MyService { 
  10.     @Override 
  11.     public void print(String info) { 
  12.         System.out.println(MyServiceA.class.getName() + " print " + info); 
  13.     } 

(2)创建第二个实现类MyServiceB

在io.binghe.spi.service.impl包下创建MyServiceB类,实现MyService接口。代码如下所示:

  1. package io.binghe.spi.service.impl; 
  2.   
  3. import io.binghe.spi.service.MyService; 
  4.   
  5. /** 
  6.  * @author binghe 
  7.  * @version 1.0.0 
  8.  * @description 接口第二个实现 
  9.  */ 
  10. public class MyServiceB implements MyService { 
  11.     @Override 
  12.     public void print(String info) { 
  13.         System.out.println(MyServiceB.class.getName() + " print " + info); 
  14.     } 

6.创建接口文件

在项目的src/main/resources目录下创建META/Services/目录,在目录中创建io.binghe.spi.service.MyService文件,注意:文件必须是接口MyService的全名,之后将实现MyService接口的类配置到文件中,如下所示:

  1. io.binghe.spi.service.impl.MyServiceA 
  2. io.binghe.spi.service.impl.MyServiceB 

7.创建测试类

在项目的io.binghe.spi.main包下创建Main类,该类为测试程序的入口类,提供一个main()方法,在main()方法中调用ServiceLoader类加载MyService接口的实现类。并通过Java8的Stream将结果打印出来,如下所示:

  1. package io.binghe.spi.main; 
  2.   
  3. import io.binghe.spi.loader.MyServiceLoader; 
  4. import io.binghe.spi.service.MyService; 
  5.   
  6. import java.util.ServiceLoader; 
  7. import java.util.stream.StreamSupport; 
  8.   
  9. /** 
  10.  * @author binghe 
  11.  * @version 1.0.0 
  12.  * @description 测试的main方法 
  13.  */ 
  14. public class Main { 
  15.   
  16.     public static void main(String[] args){ 
  17.         ServiceLoader<MyService> loader = MyServiceLoader.loadAll(MyService.class); 
  18.         StreamSupport.stream(loader.spliterator(), false).forEach(s -> s.print("Hello World")); 
  19.     } 

8.测试实例

运行Main类中的main()方法,打印出的信息如下所示:

  1. io.binghe.spi.service.impl.MyServiceA print Hello World 
  2. io.binghe.spi.service.impl.MyServiceB print Hello World 
  3.  
  4. Process finished with exit code 0 

通过打印信息可以看出,通过Java SPI机制正确加载出接口的实现类,并调用接口的实现方法。

源码解析

这里,主要是对SPI的加载流程涉及到的java.util.ServiceLoader的源码的解析。

进入java.util.ServiceLoader的源码,可以看到ServiceLoader类实现了java.lang.Iterable接口,如下所示。

  1. public final class ServiceLoader<S>  implements Iterable<S>  

说明ServiceLoader类是可以遍历迭代的。

java.util.ServiceLoader类中定义了如下的成员变量:

  1. // 加载具体实现类信息的前缀,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目录下 
  2. private static final String PREFIX = "META-INF/services/"
  3.  
  4. // 需要加载的接口 
  5. private final Class<S> service; 
  6.  
  7. // 类加载器,用于加载以接口命名的文件中配置的接口的实现类 
  8. private final ClassLoader loader; 
  9.  
  10. // 创建ServiceLoader时采用的访问控制上下文环境 
  11. private final AccessControlContext acc; 
  12.  
  13. // 用来缓存已经加载的接口实现类,其中,Key是接口实现类的完整类名,Value为实现类对象 
  14. private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); 
  15.  
  16. // 用于延迟加载实现类的迭代器 
  17. private LazyIterator lookupIterator; 

可以看到ServiceLoader类中定义了加载前缀为“META-INF/services/”,所以,接口文件必须要在项目的src/main/resources目录下的META-INF/services/目录下创建。

从MyServiceLoader类调用ServiceLoader.load(clazz)方法进入源码,如下所示:

  1. //根据类的Class对象加载指定的类,返回ServiceLoader对象 
  2. public static <S> ServiceLoader<S> load(Class<S> service) { 
  3.  //获取当前线程的类加载器 
  4.  ClassLoader cl = Thread.currentThread().getContextClassLoader(); 
  5.  //动态加载指定的类,将类加载到ServiceLoader中 
  6.  return ServiceLoader.load(service, cl); 

方法中调用了ServiceLoader.load(service, cl)方法,继续跟踪代码,如下所示:

  1. //通过ClassLoader加载指定类的Class,并将返回结果封装到ServiceLoader对象中 
  2. public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){ 
  3.  return new ServiceLoader<>(service, loader); 

可以看到ServiceLoader.load(service, cl)方法中,调用了ServiceLoader类的构造方法,继续跟进代码,如下所示:

  1. //构造ServiceLoader对象 
  2. private ServiceLoader(Class<S> svc, ClassLoader cl) { 
  3.  //如果传入的Class对象为空,则判处空指针异常 
  4.  service = Objects.requireNonNull(svc, "Service interface cannot be null"); 
  5.  //如果传入的ClassLoader为空,则通过ClassLoader.getSystemClassLoader()获取,否则直接使用传入的ClassLoader 
  6.  loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; 
  7.  acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null
  8.  reload(); 

继续跟reload()方法,如下所示。

  1. //重新加载 
  2. public void reload() { 
  3.  //清空保存加载的实现类的LinkedHashMap 
  4.  providers.clear(); 
  5.  //构造延迟加载的迭代器 
  6.  lookupIterator = new LazyIterator(service, loader); 

继续跟进懒加载迭代器的构造函数,如下所示。

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

可以看到,会将需要加载的接口的Class对象和类加载器赋值给LazyIterator的成员变量。

当我们在程序中迭代获取对象实例时,首先在成员变量providers中查找是否有缓存的实例对象。如果存在则直接返回,否则调用lookupIterator延迟加载迭代器进行加载。

迭代器进行逻辑判断的代码如下所示:

  1. //迭代ServiceLoader的方法 
  2. public Iterator<S> iterator() { 
  3.  return new Iterator<S>() { 
  4.   //获取保存实现类的LinkedHashMap<String,S>的迭代器 
  5.   Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator(); 
  6.   //判断是否有下一个元素 
  7.   public boolean hasNext() { 
  8.    //如果knownProviders存在元素,则直接返回true 
  9.    if (knownProviders.hasNext()) 
  10.     return true
  11.    //返回延迟加载器是否存在元素 
  12.    return lookupIterator.hasNext(); 
  13.   } 
  14.   //获取下一个元素 
  15.   public S next() { 
  16.    //如果knownProviders存在元素,则直接获取 
  17.    if (knownProviders.hasNext()) 
  18.     return knownProviders.next().getValue(); 
  19.    //获取延迟迭代器lookupIterator中的元素 
  20.    return lookupIterator.next(); 
  21.   } 
  22.  
  23.   public void remove() { 
  24.    throw new UnsupportedOperationException(); 
  25.   } 
  26.  }; 

LazyIterator加载类的流程如下代码所示

  1. //判断是否拥有下一个实例 
  2. private boolean hasNextService() { 
  3.  //如果拥有下一个实例,直接返回true 
  4.  if (nextName != null) { 
  5.   return true
  6.  } 
  7.  //如果实现类的全名为null 
  8.  if (configs == null) { 
  9.   try { 
  10.    //获取全文件名,文件相对路径+文件名称(包名+接口名) 
  11.    String fullName = PREFIX + service.getName(); 
  12.    //类加载器为空,则通过ClassLoader.getSystemResources()方法获取 
  13.    if (loader == null
  14.     configs = ClassLoader.getSystemResources(fullName); 
  15.    else 
  16.     //类加载器不为空,则直接通过类加载器获取 
  17.     configs = loader.getResources(fullName); 
  18.   } catch (IOException x) { 
  19.    fail(service, "Error locating configuration files", x); 
  20.   } 
  21.  } 
  22.  while ((pending == null) || !pending.hasNext()) { 
  23.   //如果configs中没有更过的元素,则直接返回false 
  24.   if (!configs.hasMoreElements()) { 
  25.    return false
  26.   } 
  27.   //解析包结构 
  28.   pending = parse(service, configs.nextElement()); 
  29.  } 
  30.  nextName = pending.next(); 
  31.  return true
  32.  
  33. private S nextService() { 
  34.  if (!hasNextService()) 
  35.   throw new NoSuchElementException(); 
  36.  String cn = nextName; 
  37.  nextName = null
  38.  Class<?> c = null
  39.  try { 
  40.   //加载类对象 
  41.   c = Class.forName(cn, false, loader); 
  42.  } catch (ClassNotFoundException x) { 
  43.   fail(service, 
  44.     "Provider " + cn + " not found"); 
  45.  } 
  46.  if (!service.isAssignableFrom(c)) { 
  47.   fail(service, 
  48.     "Provider " + cn  + " not a subtype"); 
  49.  } 
  50.  try { 
  51.   //通过c.newInstance()生成对象实例 
  52.   S p = service.cast(c.newInstance()); 
  53.   //将生成的对象实例保存到缓存中(LinkedHashMap<String,S>) 
  54.   providers.put(cn, p); 
  55.   return p; 
  56.  } catch (Throwable x) { 
  57.   fail(service, 
  58.     "Provider " + cn + " could not be instantiated"
  59.     x); 
  60.  } 
  61.  throw new Error();          // This cannot happen 
  62.  
  63. public boolean hasNext() { 
  64.  if (acc == null) { 
  65.   return hasNextService(); 
  66.  } else { 
  67.   PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() { 
  68.    public Boolean run() { return hasNextService(); } 
  69.   }; 
  70.   return AccessController.doPrivileged(action, acc); 
  71.  } 
  72.  
  73. public S next() { 
  74.  if (acc == null) { 
  75.   return nextService(); 
  76.  } else { 
  77.   PrivilegedAction<S> action = new PrivilegedAction<S>() { 
  78.    public S run() { return nextService(); } 
  79.   }; 
  80.   return AccessController.doPrivileged(action, acc); 
  81.  } 

最后,给出整个java.util.ServiceLoader的类,如下所示:

  1. package java.util; 
  2.  
  3. import java.io.BufferedReader; 
  4. import java.io.IOException; 
  5. import java.io.InputStream; 
  6. import java.io.InputStreamReader; 
  7. import java.net.URL; 
  8. import java.security.AccessControlContext; 
  9. import java.security.AccessController; 
  10. import java.security.PrivilegedAction; 
  11.  
  12.  
  13. public final class ServiceLoader<S>  implements Iterable<S> { 
  14.     // 加载具体实现类信息的前缀,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目录下 
  15.     private static final String PREFIX = "META-INF/services/"
  16.  
  17.     // 需要加载的接口 
  18.     private final Class<S> service; 
  19.      
  20.     // 类加载器,用于加载以接口命名的文件中配置的接口的实现类 
  21.     private final ClassLoader loader; 
  22.      
  23.     // 创建ServiceLoader时采用的访问控制上下文环境 
  24.     private final AccessControlContext acc; 
  25.      
  26.     // 用来缓存已经加载的接口实现类,其中,Key是接口实现类的完整类名,Value为实现类对象 
  27.     private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); 
  28.      
  29.     // 用于延迟加载实现类的迭代器 
  30.     private LazyIterator lookupIterator; 
  31.      
  32.     //重新加载 
  33.     public void reload() { 
  34.         //清空保存加载的实现类的LinkedHashMap 
  35.         providers.clear(); 
  36.         //构造延迟加载的迭代器 
  37.         lookupIterator = new LazyIterator(service, loader); 
  38.     } 
  39.      
  40.     //构造ServiceLoader对象 
  41.     private ServiceLoader(Class<S> svc, ClassLoader cl) { 
  42.         //如果传入的Class对象为空,则判处空指针异常 
  43.         service = Objects.requireNonNull(svc, "Service interface cannot be null"); 
  44.         //如果传入的ClassLoader为空,则通过ClassLoader.getSystemClassLoader()获取,否则直接使用传入的ClassLoader 
  45.         loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; 
  46.         acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null
  47.         reload(); 
  48.     } 
  49.      
  50.     private static void fail(Class<?> service, String msg, Throwable cause) 
  51.         throws ServiceConfigurationError 
  52.     { 
  53.         throw new ServiceConfigurationError(service.getName() + ": " + msg, 
  54.                                             cause); 
  55.     } 
  56.      
  57.     private static void fail(Class<?> service, String msg) 
  58.         throws ServiceConfigurationError 
  59.     { 
  60.         throw new ServiceConfigurationError(service.getName() + ": " + msg); 
  61.     } 
  62.      
  63.     private static void fail(Class<?> service, URL u, int line, String msg) 
  64.         throws ServiceConfigurationError 
  65.     { 
  66.         fail(service, u + ":" + line + ": " + msg); 
  67.     } 
  68.      
  69.     // Parse a single line from the given configuration file, adding the name 
  70.     // on the line to the names list. 
  71.     // 
  72.     private int parseLine(Class<?> service, URL u, BufferedReader r, int lc, 
  73.                           List<String> names) 
  74.         throws IOException, ServiceConfigurationError 
  75.     { 
  76.         String ln = r.readLine(); 
  77.         if (ln == null) { 
  78.             return -1; 
  79.         } 
  80.         int ci = ln.indexOf('#'); 
  81.         if (ci >= 0) ln = ln.substring(0, ci); 
  82.         ln = ln.trim(); 
  83.         int n = ln.length(); 
  84.         if (n != 0) { 
  85.             if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0)) 
  86.                 fail(service, u, lc, "Illegal configuration-file syntax"); 
  87.             int cp = ln.codePointAt(0); 
  88.             if (!Character.isJavaIdentifierStart(cp)) 
  89.                 fail(service, u, lc, "Illegal provider-class name: " + ln); 
  90.             for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) { 
  91.                 cp = ln.codePointAt(i); 
  92.                 if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) 
  93.                     fail(service, u, lc, "Illegal provider-class name: " + ln); 
  94.             } 
  95.             if (!providers.containsKey(ln) && !names.contains(ln)) 
  96.                 names.add(ln); 
  97.         } 
  98.         return lc + 1; 
  99.     } 
  100.      
  101.     private Iterator<String> parse(Class<?> service, URL u) 
  102.         throws ServiceConfigurationError 
  103.     { 
  104.         InputStream in = null
  105.         BufferedReader r = null
  106.         ArrayList<String> names = new ArrayList<>(); 
  107.         try { 
  108.             in = u.openStream(); 
  109.             r = new BufferedReader(new InputStreamReader(in"utf-8")); 
  110.             int lc = 1; 
  111.             while ((lc = parseLine(service, u, r, lc, names)) >= 0); 
  112.         } catch (IOException x) { 
  113.             fail(service, "Error reading configuration file", x); 
  114.         } finally { 
  115.             try { 
  116.                 if (r != null) r.close(); 
  117.                 if (in != nullin.close(); 
  118.             } catch (IOException y) { 
  119.                 fail(service, "Error closing configuration file", y); 
  120.             } 
  121.         } 
  122.         return names.iterator(); 
  123.     } 
  124.      
  125.     // Private inner class implementing fully-lazy provider lookupload 
  126.     private class LazyIterator 
  127.         implements Iterator<S> 
  128.     { 
  129.      
  130.         Class<S> service; 
  131.         ClassLoader loader; 
  132.         Enumeration<URL> configs = null
  133.         Iterator<String> pending = null
  134.         String nextName = null
  135.      
  136.         private LazyIterator(Class<S> service, ClassLoader loader) { 
  137.             this.service = service; 
  138.             this.loader = loader; 
  139.         } 
  140.      
  141.         //判断是否拥有下一个实例 
  142.         private boolean hasNextService() { 
  143.             //如果拥有下一个实例,直接返回true 
  144.             if (nextName != null) { 
  145.                 return true
  146.             } 
  147.             //如果实现类的全名为null 
  148.             if (configs == null) { 
  149.                 try { 
  150.                     //获取全文件名,文件相对路径+文件名称(包名+接口名) 
  151.                     String fullName = PREFIX + service.getName(); 
  152.                     //类加载器为空,则通过ClassLoader.getSystemResources()方法获取 
  153.                     if (loader == null
  154.                         configs = ClassLoader.getSystemResources(fullName); 
  155.                     else 
  156.                         //类加载器不为空,则直接通过类加载器获取 
  157.                         configs = loader.getResources(fullName); 
  158.                 } catch (IOException x) { 
  159.                     fail(service, "Error locating configuration files", x); 
  160.                 } 
  161.             } 
  162.             while ((pending == null) || !pending.hasNext()) { 
  163.                 //如果configs中没有更过的元素,则直接返回false 
  164.                 if (!configs.hasMoreElements()) { 
  165.                     return false
  166.                 } 
  167.                 //解析包结构 
  168.                 pending = parse(service, configs.nextElement()); 
  169.             } 
  170.             nextName = pending.next(); 
  171.             return true
  172.         } 
  173.      
  174.         private S nextService() { 
  175.             if (!hasNextService()) 
  176.                 throw new NoSuchElementException(); 
  177.             String cn = nextName; 
  178.             nextName = null
  179.             Class<?> c = null
  180.             try { 
  181.                 //加载类对象 
  182.                 c = Class.forName(cn, false, loader); 
  183.             } catch (ClassNotFoundException x) { 
  184.                 fail(service, 
  185.                      "Provider " + cn + " not found"); 
  186.             } 
  187.             if (!service.isAssignableFrom(c)) { 
  188.                 fail(service, 
  189.                      "Provider " + cn  + " not a subtype"); 
  190.             } 
  191.             try { 
  192.                 //通过c.newInstance()生成对象实例 
  193.                 S p = service.cast(c.newInstance()); 
  194.                 //将生成的对象实例保存到缓存中(LinkedHashMap<String,S>) 
  195.                 providers.put(cn, p); 
  196.                 return p; 
  197.             } catch (Throwable x) { 
  198.                 fail(service, 
  199.                      "Provider " + cn + " could not be instantiated"
  200.                      x); 
  201.             } 
  202.             throw new Error();          // This cannot happen 
  203.         } 
  204.      
  205.         public boolean hasNext() { 
  206.             if (acc == null) { 
  207.                 return hasNextService(); 
  208.             } else { 
  209.                 PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() { 
  210.                     public Boolean run() { return hasNextService(); } 
  211.                 }; 
  212.                 return AccessController.doPrivileged(action, acc); 
  213.             } 
  214.         } 
  215.      
  216.         public S next() { 
  217.             if (acc == null) { 
  218.                 return nextService(); 
  219.             } else { 
  220.                 PrivilegedAction<S> action = new PrivilegedAction<S>() { 
  221.                     public S run() { return nextService(); } 
  222.                 }; 
  223.                 return AccessController.doPrivileged(action, acc); 
  224.             } 
  225.         } 
  226.      
  227.         public void remove() { 
  228.             throw new UnsupportedOperationException(); 
  229.         } 
  230.      
  231.     } 
  232.      
  233.     //迭代ServiceLoader的方法 
  234.     public Iterator<S> iterator() { 
  235.         return new Iterator<S>() { 
  236.             //获取保存实现类的LinkedHashMap<String,S>的迭代器 
  237.             Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator(); 
  238.             //判断是否有下一个元素 
  239.             public boolean hasNext() { 
  240.                 //如果knownProviders存在元素,则直接返回true 
  241.                 if (knownProviders.hasNext()) 
  242.                     return true
  243.                 //返回延迟加载器是否存在元素 
  244.                 return lookupIterator.hasNext(); 
  245.             } 
  246.             //获取下一个元素 
  247.             public S next() { 
  248.                 //如果knownProviders存在元素,则直接获取 
  249.                 if (knownProviders.hasNext()) 
  250.                     return knownProviders.next().getValue(); 
  251.                 //获取延迟迭代器lookupIterator中的元素 
  252.                 return lookupIterator.next(); 
  253.             } 
  254.      
  255.             public void remove() { 
  256.                 throw new UnsupportedOperationException(); 
  257.             } 
  258.      
  259.         }; 
  260.     } 
  261.      
  262.     //通过ClassLoader加载指定类的Class,并将返回结果封装到ServiceLoader对象中 
  263.     public static <S> ServiceLoader<S> load(Class<S> service, 
  264.                                             ClassLoader loader) 
  265.     { 
  266.         return new ServiceLoader<>(service, loader); 
  267.     } 
  268.      
  269.     //根据类的Class对象加载指定的类,返回ServiceLoader对象 
  270.     public static <S> ServiceLoader<S> load(Class<S> service) { 
  271.         //获取当前线程的类加载器 
  272.         ClassLoader cl = Thread.currentThread().getContextClassLoader(); 
  273.         //动态加载指定的类,将类加载到ServiceLoader中 
  274.         return ServiceLoader.load(service, cl); 
  275.     } 
  276.      
  277.     public static <S> ServiceLoader<S> loadInstalled(Class<S> service) { 
  278.         ClassLoader cl = ClassLoader.getSystemClassLoader(); 
  279.         ClassLoader prev = null
  280.         while (cl != null) { 
  281.             prev = cl; 
  282.             cl = cl.getParent(); 
  283.         } 
  284.         return ServiceLoader.load(service, prev); 
  285.     } 
  286.      
  287.     /** 
  288.      * Returns a string describing this service. 
  289.      * 
  290.      * @return  A descriptive string 
  291.      */ 
  292.     public String toString() { 
  293.         return "java.util.ServiceLoader[" + service.getName() + "]"
  294.     } 

SPI总结

最后,对Java提供的SPI机制进行简单的总结。

优点:

能够实现项目解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。

缺点:

  • 多个并发多线程使用ServiceLoader类的实例是不安全的
  • 虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。

参考:深入理解Java中的spi机制

 本文转载自微信公众号「冰河技术」,可以通过以下二维码关注。转载本文请联系冰河技术公众号。

 

责任编辑:武晓燕 来源: 冰河技术
相关推荐

2022-05-06 08:26:32

JavaSPI机制

2021-12-11 19:00:54

Java中断机制

2011-11-30 14:35:19

JavaSPI

2023-02-15 13:57:13

JavaSPI动态扩展

2009-06-01 11:09:16

OSGI实战进阶

2023-04-28 08:42:08

Linux内核SPI驱动

2020-06-30 15:35:36

JavaSPI代码

2021-12-30 22:50:32

KafkaConsumer 源码

2022-11-02 21:45:54

SPIJava

2021-03-16 21:45:59

Python Resize机制

2024-01-31 08:41:43

异步设计项目

2020-10-30 13:30:26

SpringBoot代码应用

2021-06-11 17:26:06

代码Java网络编程

2021-06-09 08:15:50

volatileJava开发

2023-12-11 07:21:12

SPI机制插件

2021-10-11 09:41:20

React位运算技巧前端

2021-09-10 08:31:19

DubboSPI框架

2024-02-27 08:05:32

Flink分区机制数据传输

2023-08-28 07:49:24

Redisson锁机制源码

2024-02-04 09:00:00

向量查询数据检索MyScale
点赞
收藏

51CTO技术栈公众号