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     }
View Code

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         }

 

 

posted @ 2021-11-04 21:10  賣贾笔的小男孩  阅读(356)  评论(0编辑  收藏  举报