Java SPI机制详解

1. 什么是SPI机制

SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,可以用来启动框架扩展和和替换组建

比如java.sql.Driver接口,其他不同厂商够可以针对统一接口做出不同的实现,MysqlPostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务发现。

Java中SPI机制的主要是想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是:解耦

SPI 整体机制图如下:

当服务的提供者提供一种接口的实现之后,需要在classpath下的META-INF/services/目录里创建和一个以服务接口命名的文件,这个文件里的内容就是这个接口的实现类。当前他的程序需要这个服务时,就可以通过查找这个jar包(一般都是以jar包做依赖)的
META-INF/servies中的配置文件,配置文件中有这个接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了,JDK查找服务的实现工具类时:java.util.ServiceLoader

2. SPI机制的简单示例

先定一个内容搜索接口,搜索的实现可能时基于文件系统的搜索,也可能是基于数据库的搜索

  1. 定义接口

    1
    2
    3
    public interface Search {
    List<String> searchDoc(String keyword);
    }
  2. 文件搜索实现

    1
    2
    3
    4
    5
    6
    7
    public class FileSearch implements Search{
    @Override
    public List<String> searchDoc(String keyword) {
    System.out.println("文件搜索"+keyword);
    return null;
    }
    }
  3. 数据库搜索实现

    1
    2
    3
    4
    5
    6
    7
    public class DatabaseSearch implements Search{
    @Override
    public List<String> searchDoc(String keyword) {
    System.out.println("数据库搜索"+keyword);
    return null;
    }
    }
  4. 配置META-INF/services

    在resources下新建 META-INF/services目录,然后新建接口全限定名的文件com.ygb.Search,里面添加需要用到的实现类

    1
    2
    com.ygb.FileSearch
    com.ygb.DatabaseSearch

  5. 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class TestSPI {
    public static void main(String[] args) {
    ServiceLoader<Search> service = ServiceLoader.load(Search.class);
    Iterator<Search> iterator = service.iterator();
    while (iterator.hasNext()) {
    Search search = iterator.next();
    search.searchDoc("hello world");
    }
    }
    }
    //在services目录下配置了两个实现类,那么会输出:
    // 文件搜索hello world
    // 数据库搜索hello world
  6. 总结

    ServiceLoader.load(Search.class)在加载某接口时,会去META-INF/services下查找接口的全限定名文件,再根据里面的内容加载相应的实现类。

    这就是SPI思想,接口的实现由provider实现,provider只用在提交的jar包里面的META-INF/services下根据平台定义的接口新建文件,并添加相应的实现内容就可以。

3. SPI机制的应用

3.1. JDBC DriverManger

JDBC4.0之前,我们开发连接数据库的时候,通常会用Class.forName(“com.mysql.jdbc.Driver”)这句先加载数据库驱动,然后在获取数据库连接等操作。

JDBC4.0之后,直接获取连接即可,不需要采用Class.forName这种方式。

3.1.1. JDBC接口定义

首先在Java中定义了接口java.sql.Driver,并没有具体的实现,具体的都是根据不同的厂商来提供的

3.1.2. Mysql实现

在mysql的jar包mysql-connector-java-6.0.6.jar中,可以找到META-INF/services目录,该目录下会有一个名字为java.sql.Driver的文件,文件内容是com.mysql.cj.jdbc.Driver,这里面的内容就是针对Java中定义的接口的实现。#

3.1.3. Postgresql实现

同样在postgresql的jar包postgresql-42.0.0.jar中,也可以找到同样的配置文件,文件内容是org.postgresql.Driver,这是postgresql对Java的java.sql.Driver的实现。

3.1.4. 使用方法

上面说了,现在使用SPI扩展来加载具体的驱动,我们在使用数据库连接代码时,不需要再使用Class.forName()来加载驱动来,而是使用以下嗲吗

1
2
String url = "jdbc:mysq://192.168.0.1:3306/db";
Connection conn = DriverManger.getConnnection(url, username,password);

这里并没有涉及到SPI的实现,接着看下面的解析。

3.1.5. 源码实现

上面的代码没有来加载驱动的代码,怎么去确定使用那个数据库连接的驱动呢?这里就涉及到Java的SPI扩展机制来查找相关驱动的东西来,
关于驱动的查找其实都在DriverManager中,在DriverManager有一个静态代码块如下:

1
2
3
4
5
6
7
8
9
/**
* Load the initial JDBC drivers by checking the System property
* jdbc.properties and then use the {@code ServiceLoader} mechanism
* 通过检查System属性jdbc.properties和然后使用{@code ServiceLoader}机制来加载初始JDBC驱动程序
*/
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}

可以看到是加载实例化驱动的,接着看loadInitialDrivers方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
//使用SPI的ServiceLoader来加载接口的实现
//这里没有去META-INF/services目录下查找配置文件,也没有加载具体实现类,做的事情就是封装了我们的接口类型和类加载器,并初始化了一个迭代器
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
//获取迭代器
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
//遍历所有的驱动实现
//搜索classpath下以及jar包中所有的META-INF/services目录下的java.sql.Driver文件,并找到文件中的实现类的名字,此时并没有实例化具体的实现类
while(driversIterator.hasNext()) {
//这里会根据驱动名字具体实例化各个实现类
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});

println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}

上面的代码主要步骤时:

  1. 从系统变量中获取有关驱动的定义

  2. 使用SPI来获取驱动的实现

  3. 便利使用SPI获取到的具体实现,实例话各个实现类

  4. 根据第一步获取到的驱动列表来实例话具体实现类

3.2. Common-Logging

common-logging(Jakarta Commons Logging,缩写JCL)是常用的日志库门面,可以看下它是怎么解耦的

首先,日志示例是通过LogFactorygetLog(String)方法创建的

1
2
3
4
5
//private Log log =  LogFactory.getLog(TestSPI.class);

public static Log getLog(Class clazz) throws LogConfigurationException {
return getFactory().getInstance(clazz);
}

LogFactory是一个抽象类,它负责加载具体的日志实现,分析其Factory getFactory()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
public static LogFactory getFactory() throws LogConfigurationException {
// Identify the class loader we will be using
ClassLoader contextClassLoader = getContextClassLoaderInternal();

if (contextClassLoader == null) {
// This is an odd enough situation to report about. This
// output will be a nuisance on JDK1.1, as the system
// classloader is null in that environment.
if (isDiagnosticsEnabled()) {
logDiagnostic("Context classloader is null.");
}
}

// Return any previously registered factory for this class loader
LogFactory factory = getCachedFactory(contextClassLoader);
if (factory != null) {
return factory;
}

if (isDiagnosticsEnabled()) {
logDiagnostic(
"[LOOKUP] LogFactory implementation requested for the first time for context classloader " +
objectId(contextClassLoader));
logHierarchy("[LOOKUP] ", contextClassLoader);
}
// classpath根目录下寻找commons-logging.properties
Properties props = getConfigurationFile(contextClassLoader, FACTORY_PROPERTIES);

// Determine whether we will be using the thread context class loader to
// load logging classes or not by checking the loaded properties file (if any).
// classpath根目录下commons-logging.properties是否配置use_tccl
ClassLoader baseClassLoader = contextClassLoader;
if (props != null) {
String useTCCLStr = props.getProperty(TCCL_KEY);
if (useTCCLStr != null) {
if (Boolean.valueOf(useTCCLStr).booleanValue() == false) {
baseClassLoader = thisClassLoader;
}
}
}
// 这里真正开始决定使用哪个factory
// 首先,尝试查找vm系统属性org.apache.commons.logging.LogFactory,其是否指定factory
// Determine which concrete LogFactory subclass to use.
// First, try a global system property
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] Looking for system property [" + FACTORY_PROPERTY +
"] to define the LogFactory subclass to use...");
}

try {
String factoryClass = getSystemProperty(FACTORY_PROPERTY, null);
if (factoryClass != null) {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] Creating an instance of LogFactory class '" + factoryClass +
"' as specified by system property " + FACTORY_PROPERTY);
}
factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);
} else {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] No system property [" + FACTORY_PROPERTY + "] defined.");
}
}
} catch (SecurityException e) {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] A security exception occurred while trying to create an" +
" instance of the custom factory class" + ": [" + trim(e.getMessage()) +
"]. Trying alternative implementations...");
}
// ignore
} catch (RuntimeException e) {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] An exception occurred while trying to create an" +
" instance of the custom factory class" + ": [" +
trim(e.getMessage()) +
"] as specified by a system property.");
}
throw e;
}
// 第二,尝试使用java spi服务发现机制,载META-INF/services下寻找org.apache.commons.logging.LogFactory实现
if (factory == null) {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] Looking for a resource file of name [" + SERVICE_ID +
"] to define the LogFactory subclass to use...");
}
try {
// META-INF/services/org.apache.commons.logging.LogFactory, SERVICE_ID
final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID);
if( is != null ) {

BufferedReader rd;
try {
rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
} catch (java.io.UnsupportedEncodingException e) {
rd = new BufferedReader(new InputStreamReader(is));
}

String factoryClassName = rd.readLine();
rd.close();

if (factoryClassName != null && ! "".equals(factoryClassName)) {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] Creating an instance of LogFactory class " +
factoryClassName +
" as specified by file '" + SERVICE_ID +
"' which was present in the path of the context classloader.");
}
factory = newFactory(factoryClassName, baseClassLoader, contextClassLoader );
}
} else {
// is == null
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] No resource file with name '" + SERVICE_ID + "' found.");
}
}
} catch (Exception ex) {
if (isDiagnosticsEnabled()) {
logDiagnostic(
"[LOOKUP] A security exception occurred while trying to create an" +
" instance of the custom factory class" +
": [" + trim(ex.getMessage()) +
"]. Trying alternative implementations...");
}
// ignore
}
}

// Third try looking into the properties file read earlier (if found)
// 第三,尝试从classpath根目录下的commons-logging.properties中查找org.apache.commons.logging.LogFactory属性指定的factory
if (factory == null) {
if (props != null) {
if (isDiagnosticsEnabled()) {
logDiagnostic(
"[LOOKUP] Looking in properties file for entry with key '" + FACTORY_PROPERTY +
"' to define the LogFactory subclass to use...");
}
String factoryClass = props.getProperty(FACTORY_PROPERTY);
if (factoryClass != null) {
if (isDiagnosticsEnabled()) {
logDiagnostic(
"[LOOKUP] Properties file specifies LogFactory subclass '" + factoryClass + "'");
}
factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);

// TODO: think about whether we need to handle exceptions from newFactory
} else {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] Properties file has no entry specifying LogFactory subclass.");
}
}
} else {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] No properties file available to determine" + " LogFactory subclass from..");
}
}
}

// Fourth, try the fallback implementation class
// 最后,使用后备factory实现,org.apache.commons.logging.impl.LogFactoryImpl
if (factory == null) {
if (isDiagnosticsEnabled()) {
logDiagnostic(
"[LOOKUP] Loading the default LogFactory implementation '" + FACTORY_DEFAULT +
"' via the same classloader that loaded this LogFactory" +
" class (ie not looking in the context classloader).");
}
factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader);
}

if (factory != null) {
/**
* Always cache using context class loader.
*/
cacheFactory(contextClassLoader, factory);

if (props != null) {
Enumeration names = props.propertyNames();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
String value = props.getProperty(name);
factory.setAttribute(name, value);
}
}
}

return factory;
}

可以看出,抽象类LogFactory加载具体实现的步骤如下:

  • 从vm系统属性org.apache.commons.logging.LogFactory
  • 使用SPI服务发现机制,发现org.apache.commons.logging.LogFactory的实现
  • 查找classpath根目录commons-logging.properties的org.apache.commons.logging.LogFactory属性是否指定factory实现
  • 使用默认factory实现,org.apache.commons.logging.impl.LogFactoryImpl

LogFactory的getLog()方法返回类型是org.apache.commons.logging.Log接口,提供了从trace到fatal方法。可以确定,如果日志实现提供者只要实现该接口,并且使用继承自org.apache.commons.logging.LogFactory的子类创建Log,必然可以构建一个松耦合的日志系统。

3.3. Spring中的SPI机制

在springboot的自动装配过程中,最终会加载META-INF/spring.factories文件,而加载的过程是由SpringFactoriesLoader加载的。从CLASSPATH下的每个Jar包中搜寻所有META-INF/spring.factories配置文件,然后将解析properties文件,找到指定名称的配置后返回。需要注意的是,其实这里不仅仅是会去ClassPath路径下查找,会扫描所有路径下的Jar包,只不过这个文件只会在Classpath下的jar包中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// spring.factories文件的格式为:key=value1,value2,value3
// 从所有的jar包中找到META-INF/spring.factories文件
// 然后从文件中解析出key=factoryClass类名称的所有value值
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
// 取得资源文件的URL
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
// 遍历所有的URL
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// 根据资源文件URL解析properties文件,得到对应的一组@Configuration类
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
// 组装数据,并返回
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}

4. SPI机制深入理解

4.1. SPI机制通常怎么使用

看完上面的几个例子解析,应该都知道大致的流程了:

  • 组织或公司定义标准

    • 就是定义接口,比如java.sql.Driver
  • 具体厂商或框架开发者实现

    • META-INF/services目录下定一个名字为接口全限定名的文件,文件内容是具体实现类的全限定名,比如com.cj.mysql.Driver
  • 使用

    • 引用jar来实现功能
      1
      2
      3
      4
      5
      6
      7
      8
      ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
      //获取迭代器
      Iterator<Driver> driversIterator = loadedDrivers.iterator();
      //遍历
      while(driversIterator.hasNext()) {
      driversIterator.next();
      //可以做具体的业务逻辑
      }
  • 规范

    • 总结以下jdk中SPI需要遵循的规范

4.2. SPI和API的区别

SPI: “接口”位于调用方所在的包中

  • 概念上更依赖调用方

  • 位于调用方所在的包中

  • 实现位于独立的包中

  • 常见的例子:插件模式的插件

API: “接口”位于实现方所在的包中

  • 概念上更接近实现方

  • 位于实现方所在的包中

  • 实现和接口在一个包中

4.3. SPI机制实现原理

看下JDK中ServiceLoader<S>方法的具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
//ServiceLoader实现了Iterable接口,可以遍历所有的服务实现者
public final class ServiceLoader<S>
implements Iterable<S>
{

//查找配置文件的目录
private static final String PREFIX = "META-INF/services/";

//表示要被加载的服务的类或接口
private final Class<S> service;

//这个ClassLoader用来定位,加载,实例化服务提供者
private final ClassLoader loader;

// 访问控制上下文
private final AccessControlContext acc;

// 缓存已经被实例化的服务提供者,按照实例化的顺序存储
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();

// 迭代器
private LazyIterator lookupIterator;


//重新加载,就相当于重新创建ServiceLoader了,用于新的服务提供者安装到正在运行的Java虚拟机中的情况。
public void reload() {
//清空缓存中所有已实例化的服务提供者
providers.clear();
//新建一个迭代器,该迭代器会从头查找和实例化服务提供者
lookupIterator = new LazyIterator(service, loader);
}

//私有构造器
//使用指定的类加载器和服务创建服务加载器
//如果没有指定类加载器,使用系统类加载器,就是应用类加载器。
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}

//解析失败处理的方法
private static void fail(Class<?> service, String msg, Throwable cause)
throws ServiceConfigurationError
{
throw new ServiceConfigurationError(service.getName() + ": " + msg,
cause);
}

private static void fail(Class<?> service, String msg)
throws ServiceConfigurationError
{
throw new ServiceConfigurationError(service.getName() + ": " + msg);
}

private static void fail(Class<?> service, URL u, int line, String msg)
throws ServiceConfigurationError
{
fail(service, u + ":" + line + ": " + msg);
}

//解析服务提供者配置文件中的一行
//首先去掉注释校验,然后保存
//返回下一行行号
//重复的配置项和已经被实例化的配置项不会被保存
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
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}

//使用线程上下文的类加载器来创建ServiceLoader
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}

//使用扩展类加载器为指定的服务创建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);
}

public String toString() {
return "java.util.ServiceLoader[" + service.getName() + "]";
}

}
  1. 首先,ServiceLoader实现了Iterable接口,所以它有迭代器的属性,这里主要都是实现了迭代器的hasNext和next方法。这里主要都是调用的lookupIterator的相应hasNext和next方法,lookupIterator是懒加载迭代器。

  2. 其次,LazyIterator中的hasNext方法,静态变量PREFIX就是”META-INF/services/”目录,这也就是为什么需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件。

  3. 最后,通过反射方法Class.forName()加载类对象,并用newInstance方法将类实例化,并把实例化后的类缓存到providers对象中,(LinkedHashMap<String,S>类型)然后返回实例对象

4.4. SPI缺陷

通过上面的解析,可以发现,我们使用SPI机制的缺陷:

  1. 不能按需加载,需要遍历所有的实现,并实例话,然后在循环中才能找到我们需要的实现,如果不像用某些实现类,或则某些类实例化很耗时,它也被载入实例化,造成浪费

  2. 获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类

  3. 多个并发多线程使用ServiceLoader类的示例是不安全的

参考文章:Java常用机制 - SPI机制详解