Java:SPI机制
一、SPI是什么?
SPI全称为Service Provider Interface,是一种服务发现机制。SPI的本质是将接口的全限定类名配置在文件中,并由服务加载器 ServiceLoader 读取配置文件,加载实现类。这样可以再运行的时候,动态的替换接口的实现类。我们可以通过SPI的这种机制为我们的程序提供拓展功能。常见的SPI包括JDBC、日志门面接口、Spring、SpringBook相关的starter组件、Dubbo、JNDI等
二、关于SPI的一些概念
服务是一组公共的接口和类(通常是抽象类),提供对某些特定应用程序功能或特性。
服务服务提供者接口,服务定义的一组公共接口和抽象类。SPI 定义了可用于您的应用程序的类和方法。
服务提供者是服务的特定实现,比如说一个APP上需要支付,日常生活中常见的支付方式有支付宝支付和微信支付,这两种支付方式都是支付的服务提供者。
多个相同的服务,为同一个相同的服务提供多个程序,系统可以任意选择其中一个服务提供者,用户可以选择已安装的程序的一个。
服务提供者以特殊格式的 JAR 文件提供他们的新服务,通过位于资源目录 META-INF/services 中的提供文件标志服务提供者,配置文件的名称是服务提供者的全限定类名,其中名称的每个组成部分用句点 (.
) 分隔。该文件必须采用 UTF-8 编码。此外,还可以通过以数字符号 (#)开头的注释行来在文件中包含注释。然后服务接口的实现类的jar包要放在主程序的Classpath下,这样服务启动时,主程序就会用ServiceLoader动态加载实现模块,扫描所有jar包中的约定好的类名,调用Class.forname加载。
三、原理分析
服务加载器 ServiceLoader 的源码的成员变量。
1 public final class ServiceLoader<S> 2 implements Iterable<S> 3 { 4 5 private static final String PREFIX = "META-INF/services/"; 6 7 // 表示正在加载的服务的类或接口 8 private final Class<S> service; 9 10 // 用于定位、加载和实例化提供者的类加载器 11 private final ClassLoader loader; 12 13 // 创建 ServiceLoader 时获取的访问控制上下文 14 private final AccessControlContext acc; 15 16 // 缓存提供程序,按实例化顺序 17 private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); 18 19 // 当前的懒加载查找迭代器 20 private LazyIterator lookupIterator; 21 ... ... 22 }
ServiceLoader的私有构造方法,服务的接口,类加载器。
1 private ServiceLoader(Class<S> svc, ClassLoader cl) { 2 service = Objects.requireNonNull(svc, "Service interface cannot be null"); 3 loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; 4 acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; 5 reload(); 6 }
ServiceLoader的 load 方法,使用了单例模式?
1 public static <S> ServiceLoader<S> load(Class<S> service, 2 ClassLoader loader) 3 { 4 return new ServiceLoader<>(service, loader); 5 }
reload 方法,清除次加载程序的所有缓存,重新加载所有的提供程序,并且实例了一个懒加载查找迭代器,等到主程序需要加载提供者程序时,才会使用类加载器去查找所有jar包的服务提供者的class,实例化服服务提供程序并缓存起来。
1 public void reload() { 2 providers.clear(); 3 lookupIterator = new LazyIterator(service, loader); 4 }
ServiceLoader的私有内部类,用来懒加载服务提供者程序。
1 private class LazyIterator 2 implements Iterator<S> 3 { 4 5 Class<S> service; 6 ClassLoader loader; 7 Enumeration<URL> configs = null; 8 Iterator<String> pending = null; 9 String nextName = null; 10 11 private LazyIterator(Class<S> service, ClassLoader loader) { 12 this.service = service; 13 this.loader = loader; 14 } 15 16 private boolean hasNextService() { 17 if (nextName != null) { 18 return true; 19 } 20 if (configs == null) { 21 try { 22 String fullName = PREFIX + service.getName(); 23 if (loader == null) 24 configs = ClassLoader.getSystemResources(fullName); 25 else 26 configs = loader.getResources(fullName); 27 } catch (IOException x) { 28 fail(service, "Error locating configuration files", x); 29 } 30 } 31 while ((pending == null) || !pending.hasNext()) { 32 if (!configs.hasMoreElements()) { 33 return false; 34 } 35 pending = parse(service, configs.nextElement()); 36 } 37 nextName = pending.next(); 38 return true; 39 } 40 41 private S nextService() { 42 if (!hasNextService()) 43 throw new NoSuchElementException(); 44 String cn = nextName; 45 nextName = null; 46 Class<?> c = null; 47 try { 48 c = Class.forName(cn, false, loader); 49 } catch (ClassNotFoundException x) { 50 fail(service, 51 "Provider " + cn + " not found"); 52 } 53 if (!service.isAssignableFrom(c)) { 54 fail(service, 55 "Provider " + cn + " not a subtype"); 56 } 57 try { 58 S p = service.cast(c.newInstance()); 59 providers.put(cn, p); 60 return p; 61 } catch (Throwable x) { 62 fail(service, 63 "Provider " + cn + " could not be instantiated", 64 x); 65 } 66 throw new Error(); // This cannot happen 67 } 68 69 public boolean hasNext() { 70 if (acc == null) { 71 return hasNextService(); 72 } else { 73 PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() { 74 public Boolean run() { return hasNextService(); } 75 }; 76 return AccessController.doPrivileged(action, acc); 77 } 78 } 79 80 public S next() { 81 if (acc == null) { 82 return nextService(); 83 } else { 84 PrivilegedAction<S> action = new PrivilegedAction<S>() { 85 public S run() { return nextService(); } 86 }; 87 return AccessController.doPrivileged(action, acc); 88 } 89 } 90 91 public void remove() { 92 throw new UnsupportedOperationException(); 93 } 94 95 }
ServiceLoader首先会去确认是否有缓存有实例对象,有则直接从缓存中返回。没有则会执行懒加载的方法,从使用反射方法,从ClassLoader加载并实例化服务提供者,并放入缓存。
1 public Iterator<S> iterator() { 2 return new Iterator<S>() { 3 4 Iterator<Map.Entry<String,S>> knownProviders 5 = providers.entrySet().iterator(); 6 7 public boolean hasNext() { 8 if (knownProviders.hasNext()) 9 return true; 10 return lookupIterator.hasNext(); 11 } 12 13 public S next() { 14 if (knownProviders.hasNext()) 15 return knownProviders.next().getValue(); 16 return lookupIterator.next(); 17 } 18 19 public void remove() { 20 throw new UnsupportedOperationException(); 21 } 22 23 }; 24 }
ServiceLoader可以跨jar包获取META-INF/services目录中的文件,读取文件得到所有可以实例化的类的名称。
使用反射方法,Class.forName()用于加载类对象,instance()用于实例化类。最后放入缓存providers中,返回实例的服务提供者对象。
1 private S nextService() { 2 if (!hasNextService()) 3 throw new NoSuchElementException(); 4 String cn = nextName; 5 nextName = null; 6 Class<?> c = null; 7 try { 8 c = Class.forName(cn, false, loader); 9 } catch (ClassNotFoundException x) { 10 fail(service, 11 "Provider " + cn + " not found"); 12 } 13 if (!service.isAssignableFrom(c)) { 14 fail(service, 15 "Provider " + cn + " not a subtype"); 16 } 17 try { 18 S p = service.cast(c.newInstance()); 19 providers.put(cn, p); 20 return p; 21 } catch (Throwable x) { 22 fail(service, 23 "Provider " + cn + " could not be instantiated", 24 x); 25 } 26 throw new Error(); // This cannot happen 27 }