1. SPI机制

SPI 全称为 Service Provider Interface,是一种服务发现机制。

SPI 的本质是将接口实现类全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态为接口替换实现类。正因此特性,我们可以很容易的通过 SPI 机制为我们的程序提供拓展功能。

2. Java SPI

2.1 demo

首先,定义接口;

1
2
3
4
public interface DataStorage {

String search(String key);
}

其次,定义两个对该接口的实现类;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 1. msyql存储实现类
public class MysqlStorage implements DataStorage {

@Override
public String search(String key) {
return "【Mysql】搜索" + key + ",结果:No";
}

}

// 2. redis存储实现类
public class RedisStorage implements DataStorage {
@Override
public String search(String key) {
return "【Redis】搜索" + key + ",结果:Yes";
}
}

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

1
2
com.smy.spi.MysqlStorage
com.smy.spi.RedisStorage

最后,使用ServiceLoader加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SpiDemo {
public static void main(String[] args) {

ServiceLoader<DataStorage> serviceLoader = ServiceLoader.load(DataStorage.class);
System.out.println("============ Java SPI 测试============");
// 1. forEach模式
serviceLoader.forEach(loader -> System.out.println(loader.search("Yes Or No")));
// 2. 迭代器模式
Iterator<Robot> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
DataStorage dataStorage = iterator.next();
robot.search();
}
}

}

2.2 原理

ServiceLoader的load方法

1
2
3
4
5
6
7
8
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}

public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){
return new ServiceLoader<>(service, loader);
}

创建一个 ServiceLoader 对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 LinkedHashMap<String,S> providers = new LinkedHashMap<>();

public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}

LazyIterator

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
private class LazyIterator implements Iterator<S>
{

Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;

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

// 1. hasNextService
// 通过加载器通过文件全名获取配置对象 Enumeration<URL> configs ,然后调用解析parse方法解析classpath下的META-INF/services/目录里以服务接口命名的文件。
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
// PREFIX = "META-INF/services/";
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
// 解析配置文件
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}

// 2. nextService
// 反射方法 Class.forName() 加载类对象,并用newInstance方法将类实例化,并把实例化后的类缓存到providers对象中,(LinkedHashMap<String,S>类型,然后返回实例对象
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}

// 3. hasNext
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);
}
}

// 4. next
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);
}
}

// 5. remove
public void remove() {
throw new UnsupportedOperationException();
}

}

2.3 缺陷

  1. 不能按需加载,需要遍历所有的实现,并实例化,然后在循环中才能找到我们需要的实现。如果不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。
  2. 获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。
  3. 多个并发多线程使用 ServiceLoader 类的实例是不安全的。

2.4 JDBC DriverManager

DriverManager加载驱动流程:

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

  2. 使用 SPI 来获取驱动的实现类(字符串的形式)。

  3. 遍历使用 SPI 获取到的具体实现,实例化各个实现类。

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

首先在JDBC4.1之前,如果需要连接数据,需要先加载数据库相关的驱动,然后在进行数据库的连接操作;

1
2
3
4
5
 //STEP1 : Register JDBC driver
Class.forName("com.mysql.jdbc.Driver");
//STEP2 :Open a connection
String url = "jdbc:xxxx://xxxx:xxxx/xxxx";
Connection conn = DriverManager.getConnection(url,username,password);

其次,在JDBC4.1之后使用了Java的SPI机制,可以直接使用JDBC驱动。

1
2
3
4
5
6
7
8
/**
* Load the initial JDBC drivers by checking the System property
* jdbc.properties and then use the {@code ServiceLoader} mechanism
*/
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}

在 Java 中,static 块用于静态初始化,它在类被加载到 Java 虚拟机中时执行。静态块会加载实例化驱动,接下来我们看看 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;
// 1. 从系统变量中获取有关驱动的定义
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
// 2. 使用SPI来获取驱动的实现
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// 3. 遍历使用SPI获取到的具体实现类,实例化实现类
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
// 4. 遍历使用SPI获取到的具体实现,实例化各个实现类
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});

println("DriverManager.initialize: jdbc.drivers = " + drivers);

if (drivers == null || drivers.equals("")) {
return;
}
// 4. 根据第一步获取到的驱动列表来实例化具体的实现类
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);
}
}
}

3. Spring SPI

3.1 demo

Spring SPI 沿用了 Java SPI 的设计思想,Spring 采用的是 spring.factories 方式实现 SPI 机制,可以在不修改 Spring 源码的前提下,提供 Spring 框架的扩展性。

  • 创建 MyTestService 接口

    1
    2
    3
    4
    5
    public interface MyTestService{

    void printMylife();

    }
  • 创建 MyTestService 接口实现类

    1. WorkTestService

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      public class WorkTestService implements MyTestService{

      public WorkTestService(){

      System.out.println("WorkTestService");

      }

      public void printMylife(){

      System.out.println("我的工作");

      }

      }
    2. FamilyTestService

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      public class FamilyTestService implements MyTestService{

      public FamilyTestService(){

      System.out.println("FamilyTestService");

      }

      public void printMylife(){

      System.out.println("我的家庭");

      }

      }


  • 在资源文件目录,创建一个固定的文件 META-INF/spring.factories。

    1
    2
    3
    #key是接口的全限定名,value是接口的实现类

    com.courage.platform.sms.demo.service.MyTestService = com.courage.platform.sms.demo.service.impl.FamilyTestService,com.courage.platform.sms.demo.service.impl.WorkTestService
  • 运行代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
      //调用SpringFactoriesLoader.loadFactories方法加载MyTestService接口所有实现类的实例


    List<MyTestService> myTestServices = SpringFactoriesLoader.loadFactories(
    MyTestService.class,
    Thread.currentThread().getContextClassLoader()
    );

    for(MyTestService testService : myTestServices){
    testService.printMylife();
    }

运行结果:

1
2
3
4
  FamilyTestService
WorkTestService
我的家庭
我的工作

Spring SPI 机制非常类似 ,但还是有一些差异:

  1. Java SPI 是一个服务提供接口对应一个配置文件,配置文件中存放当前接口的所有实现类,多个服务提供接口对应多个配置文件,所有配置都在 services 目录下。
  2. Spring SPI 是一个 spring.factories 配置文件存放多个接口及对应的实现类,以接口全限定名作为key,实现类作为value来配置,多个实现类用逗号隔开,仅 spring.factories 一个配置文件。

和 Java SPI 一样,Spring SPI 也无法获取某个固定的实现,只能按顺序获取所有实现

4. Dubbo SPI

4.2 demo

基于 Java SPI 的缺陷无法支持按需加载接口实现类,Dubbo 并未使用 Java SPI,而是重新实现了一套功能更强的 SPI 机制。

Dubbo SPI 的相关逻辑被封装在了 ExtensionLoader 类中,通过 ExtensionLoader,我们可以加载指定的实现类。

Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路径下,配置内容如下:

1
2
optimusPrime = org.apache.spi.OptimusPrime
bumblebee = org.apache.spi.Bumblebee

与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。

另外,在测试 Dubbo SPI 时,需要在 Robot 接口上标注 @SPI 注解。

下面来演示 Dubbo SPI 的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class DubboSPITest{

@Test
public void sayHello() throws Exception{

ExtensionLoader<Robot> extensionLoader = ExtensionLoader.getExtensionLoader(Robot.class);
Robot optimusPrime = extensionLoader.getExtension("optimusPrime");
optimusPrime.sayHello();
Robot bumblebee = extensionLoader.getExtension("bumblebee");
bumblebee.sayHello();

}
}

5. 自定义类加载器

5.1 扩展类加载器

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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
public class ExtensionLoader<T> {

private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class);

private static final String SERVICES_DIRECTORY = "META-INF/services/";

private static final String SMS_DIRECTORY = "META-INF/sms/";

private static final String DEFAULT_CLASSLOADER_POLICY = "internal";

private static final String WORKER_DIR_NAME = "platform-sms-admin";

private static final Pattern NAME_SEPARATOR = Pattern
.compile("\\s*[,]+\\s*");

private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>();

private static final ConcurrentMap<Class<?>, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>();

private static final ConcurrentMap<String, Object> EXTENSION_KEY_INSTANCE = new ConcurrentHashMap<>();

private final Class<?> type;

private final String classLoaderPolicy;

private final ConcurrentMap<Class<?>, String> cachedNames = new ConcurrentHashMap<>();

private final Holder<Map<String, Class<?>>> cachedClasses = new Holder<>();

private final ConcurrentMap<String, Holder<Object>> cachedInstances = new ConcurrentHashMap<>();

private String cachedDefaultName;

private ConcurrentHashMap<String, IllegalStateException> exceptions = new ConcurrentHashMap<>();

private static <T> boolean withExtensionAnnotation(Class<T> type) {
return type.isAnnotationPresent(SPI.class);
}

public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) {
return getExtensionLoader(type, DEFAULT_CLASSLOADER_POLICY);
}

@SuppressWarnings("unchecked")
public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type, String classLoaderPolicy) {
if (type == null) throw new IllegalArgumentException("Extension type == null");
if (!type.isInterface()) {
throw new IllegalArgumentException("Extension type(" + type + ") is not interface!");
}
if (!withExtensionAnnotation(type)) {
throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @"
+ SPI.class.getSimpleName() + " Annotation!");
}

ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
if (loader == null) {
EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type, classLoaderPolicy));
loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type);
}
return loader;
}

private ExtensionLoader(Class<?> type, String classLoaderPolicy) {
this.type = type;
this.classLoaderPolicy = classLoaderPolicy;
}

/**
* 返回指定名字的扩展
*
* @param name
* @return
*/
@SuppressWarnings("unchecked")
public T getExtension(String name) {
if (name == null || name.length() == 0) {
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<>());
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;
}

public T getExtensionDirectly(String name) {
if (name == null || name.length() == 0) {
throw new IllegalArgumentException("Extension name == null");
}
return createExtensionDirectly(name);
}

@SuppressWarnings("unchecked")
public T getExtension(String name, String key) {
if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null");
if ("true".equals(name)) {
return getDefaultExtension();
}
String extKey = name + "-" + StringUtils.trimToEmpty(key);
Holder<Object> holder = cachedInstances.get(extKey);
if (holder == null) {
cachedInstances.putIfAbsent(extKey, new Holder<>());
holder = cachedInstances.get(extKey);
}
Object instance = holder.get();
if (instance == null) {
synchronized (holder) {
instance = holder.get();
if (instance == null) {
instance = createExtension(name, key);
holder.set(instance);
}
}
}
return (T) instance;
}

/**
* 返回缺省的扩展,如果没有设置则返回<code>null</code>
*/
public T getDefaultExtension() {
getExtensionClasses();
if (null == cachedDefaultName || cachedDefaultName.length() == 0 || "true".equals(cachedDefaultName)) {
return null;
}
return getExtension(cachedDefaultName);
}

// public private method 分界线

@SuppressWarnings("unchecked")
private T createExtension(String name) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type
+ ") could not be instantiated: class could not be found");
}
try {
T instance = (T) EXTENSION_INSTANCES.get(clazz);
if (instance == null) {
EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance());
instance = (T) EXTENSION_INSTANCES.get(clazz);
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type
+ ") could not be instantiated: " + t.getMessage(),
t);
}
}

@SuppressWarnings("unchecked")
private T createExtension(String name, String key) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type
+ ") could not be instantiated: class could not be found");
}
try {
T instance = (T) EXTENSION_KEY_INSTANCE.get(name + "-" + key);
if (instance == null) {
EXTENSION_KEY_INSTANCE.putIfAbsent(name + "-" + key, clazz.newInstance());
instance = (T) EXTENSION_KEY_INSTANCE.get(name + "-" + key);
}
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type
+ ") could not be instantiated: " + t.getMessage(),
t);
}
}

private T createExtensionDirectly(String name) {
Class<?> clazz = getExtensionClasses().get(name);
if (clazz == null) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type
+ ") could not be instantiated: class could not be found");
}
try {
T instance = (T) clazz.newInstance();
return instance;
} catch (Throwable t) {
throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type
+ ") could not be instantiated: " + t.getMessage(),
t);
}
}

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

private String getJarDirectoryPath() {
String userDir = System.getProperty("user.dir");
URL url = Thread.currentThread().getContextClassLoader().getResource("");
String dirtyPath;
if (url != null) {
dirtyPath = url.toString();
} else {
File file = new File("");
dirtyPath = file.getAbsolutePath();
}
String jarPath = dirtyPath.replaceAll("^.*file:/", ""); // removes
// file:/ and
// everything
// before it
jarPath = jarPath.replaceAll("jar!.*", "jar"); // removes everything
// after .jar, if .jar
// exists in dirtyPath
jarPath = jarPath.replaceAll("%20", " "); // necessary if path has
// spaces within
if (!jarPath.endsWith(".jar")) { // this is needed if you plan to run
// the app using Spring Tools Suit play
// button.
jarPath = jarPath.replaceAll("/classes/.*", "/classes/");
}
logger.info("jarPath:" + jarPath);
Path path = Paths.get(jarPath).getParent(); // Paths - from java 8
if (path != null) {
logger.info("getJarDirectoryPath:" + path);
return path.toString();
}
return null;
}

private Map<String, Class<?>> loadExtensionClasses() {
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 jarDirectoryPath = this.getJarDirectoryPath();
// compatible with jdk17
if (jarDirectoryPath.endsWith("lib") || jarDirectoryPath.endsWith("bin")) {
jarDirectoryPath = jarDirectoryPath.replaceAll("lib", "").replace("bin", "");
}

// 1. plugin folder,customized extension classLoader (jar_dir/plugin)
String dir = File.separator + jarDirectoryPath + File.separator + "plugin";

logger.info("dir:" + dir);

File externalLibDir = new File(dir);
if (!externalLibDir.exists()) {
externalLibDir = new File(File.separator + jarDirectoryPath + File.separator + WORKER_DIR_NAME
+ File.separator + "plugin");
}
logger.info("extension classpath dir: " + externalLibDir.getAbsolutePath());
if (externalLibDir.exists()) {
File[] files = externalLibDir.listFiles((dir1, name) -> name.endsWith(".jar"));
if (files != null) {
for (File f : files) {
URL url;
try {
url = f.toURI().toURL();
} catch (MalformedURLException e) {
throw new RuntimeException("load extension jar failed!", e);
}

ClassLoader parent = Thread.currentThread().getContextClassLoader();
URLClassLoader localClassLoader;
if (classLoaderPolicy == null || "".equals(classLoaderPolicy)
|| DEFAULT_CLASSLOADER_POLICY.equalsIgnoreCase(classLoaderPolicy)) {
localClassLoader = new URLClassExtensionLoader(new URL[]{url});
} else {
localClassLoader = new URLClassLoader(new URL[]{url}, parent);
}

loadFile(extensionClasses, SMS_DIRECTORY, localClassLoader);
loadFile(extensionClasses, SERVICES_DIRECTORY, localClassLoader);
}
}
}
// 只加载外部spi, 不加载classpath
// 2. load inner extension class with default classLoader
// ClassLoader classLoader = findClassLoader();
// loadFile(extensionClasses, SMS_DIRECTORY, classLoader);
// loadFile(extensionClasses, SERVICES_DIRECTORY, classLoader);

return extensionClasses;
}

private void loadFile(Map<String, Class<?>> extensionClasses, String dir, ClassLoader classLoader) {
String fileName = dir + type.getName();
try {
Enumeration<URL> urls;
if (classLoader != null) {
urls = classLoader.getResources(fileName);
} else {
urls = ClassLoader.getSystemResources(fileName);
}
if (urls != null) {
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
try {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8))) {
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) {
Class<?> clazz = classLoader.loadClass(line);
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException(
"Error when load extension class(interface: " + type
+ ", class line: " + clazz.getName()
+ "), class " + clazz.getName()
+ "is not subtype of interface.");
} else {
try {
clazz.getConstructor(type);
} catch (NoSuchMethodException e) {
clazz.getConstructor();
String[] names = NAME_SEPARATOR.split(name);
if (names != null && names.length > 0) {
for (String n : names) {
if (!cachedNames.containsKey(clazz)) {
cachedNames.put(clazz, n);
}
Class<?> c = extensionClasses.get(n);
if (c == null) {
extensionClasses.put(n, clazz);
} else if (c != clazz) {
cachedNames.remove(clazz);
throw new IllegalStateException(
"Duplicate extension " + type.getName() + " name "
+ n + " on "
+ c.getName() + " and "
+ clazz.getName());
}
}
}
}
}
}
} catch (Throwable t) {
IllegalStateException e = new IllegalStateException(
"Failed to load extension class(interface: " + type + ", class line: "
+ line + ") in " + url
+ ", cause: "
+ t.getMessage(),
t);
exceptions.put(line, e);
}
}
} // end of while read lines
}
} catch (Throwable t) {
logger.error("Exception when load extension class(interface: " + type + ", class file: " + url
+ ") in " + url,
t);
}
} // end of while urls
}
} catch (Throwable t) {
logger.error(
"Exception when load extension class(interface: " + type + ", description file: " + fileName + ").",
t);
}
}

@SuppressWarnings("unused")
private static ClassLoader findClassLoader() {
return ExtensionLoader.class.getClassLoader();
}

@Override
public String toString() {
return this.getClass().getName() + "[" + type.getName() + "]";
}

private static class Holder<T> {

private volatile T value;

private void set(T value) {
this.value = value;
}

private T get() {
return value;
}

}
}

5.2 使用

  • 第一步:创建静态加载器 ExtensionLoader

    1
    private static ExtensionLoader<OuterAdapter> EXTENSION_LOADER = ExtensionLoader.getExtensionLoader(OuterAdapter.class);
  • 第二步:通过加载器生成适配器对象

    1
    OuterAdapter adapter = EXTENSION_LOADER.getExtensionDirectly(adapterName);
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
@Component
public class AdapterLoader {

private final static Logger logger = LoggerFactory.getLogger(AdapterLoader.class);

// 第一步
private static ExtensionLoader<OuterAdapter> EXTENSION_LOADER = ExtensionLoader.getExtensionLoader(OuterAdapter.class);

private static ConcurrentHashMap<Integer, OuterAdapter> ADAPTER_MAP = new ConcurrentHashMap<>(16);

public void loadAdapter(SmsChannelConfig smsChannelConfig) {
String adapterName = smsChannelConfig.getChannelType();
try {
// 第二步
// 直接通过 Class 创建实例对象
OuterAdapter adapter = EXTENSION_LOADER.getExtensionDirectly(adapterName);
ClassLoader cl = Thread.currentThread().getContextClassLoader();
// 替换 ClassLoader
Thread.currentThread().setContextClassLoader(adapter.getClass().getClassLoader());
adapter.init(smsChannelConfig);
Thread.currentThread().setContextClassLoader(cl);
// 卸载之前的适配器
OuterAdapter preAdapter = ADAPTER_MAP.get(smsChannelConfig.getId());
if (preAdapter != null) {
preAdapter.destroy();
}
// 加入到容器里
ADAPTER_MAP.put(smsChannelConfig.getId(), adapter);
logger.info("Load sms adapter: {} succeed", adapterName);
} catch (Exception e) {
logger.error("Load sms adapter: {} failed", adapterName, e);
}
}

public OuterAdapter getAdapterByChannelId(Integer id) {
return ADAPTER_MAP.get(id);
}

}

本站由 卡卡龙 使用 Stellar 1.29.1主题创建

本站访问量 次. 本文阅读量 次.