背景:
- 经常存在一种场景:先定义接口,然后针对接口有不同的实现。具体使用哪种实现,最后由业务来定
- 我们期望定义的接口,在上传一个jar包后,就能加载jar包中对应的实现,并且功能切换到这种实现上
- 实现可以交给第三方来实现,第三方按照接口实现后,拿到我们系统加载使用即可
总结:需要一种即插即用的类加载
主角:ServiceLoader
ServiceLoader可以加载类实现,需要如下步骤:
- 定义接口
- 定义实现
- 定义META-INF/services 并在该目录下配置接口的实现类
- 使用ServiceLoader加载
分3个模块分别定义了接口,实现1和实现2.并且在定义一个模块用于加载接口实现。
接口
package com.zhangbo.hello;
public interface SayHello {
String execute(String name);
}
实现1
package com.zhangbo;
import com.zhangbo.hello.SayHello;
public class ChineseSimple implements SayHello {
@Override
public String execute(String name) {
return "优美的中国话";
}
}
实现2
package com.zhangbo;
import com.zhangbo.hello.SayHello;
/**
* Hello world!
*
*/
public class English implements SayHello {
@Override
public String execute(String name) {
return "hello, welcom to en," + name;
}
}
main模块
也实现了一种
package com.zhangbo.hello;
public class Default implements SayHello {
@Override
public String execute(String name) {
return name;
}
}
main模块的META-INF/services目录下定义文件:com.zhangbo.hello.SayHello
文件内容
com.zhangbo.hello.Default
main函数如下:
ServiceLoader<SayHello> loads = ServiceLoader.load(SayHello.class);
loads.forEach(e -> {
System.out.println(e.execute("23456tygh"));
});
System.out.println("finish");
控制台打印:
23456tygh
finish
如果我们想加载英文或者中文版的实现。首先需要把对应的class加载进来,然后在com.zhangbo.hello.SayHello文件中增加对应类名。
幸运的是 META-INF是可以每个jar单独定义的。因此可以有这样2种思路:
首先在中文,英文实现的resouces下面增加 META-INF/servicescom.zhangbo.hello.SayHello 文件内容分别是
com.zhangbo.ChineseSimple
com.zhangbo.English
然后有2种思路:1种是将对应jar包通过pom引入,这种类似mysql-connect-java的jar加载。另一种是通过ClassLoader加载
File dir = new File("D:\\zhangbo-workspace\\zhangbo\\dynamic-load");
File[] files = dir.listFiles();
List<URL> jars = Arrays.stream(files).filter(file -> file.getName().endsWith("jar")).map(file -> {
try {
return new URL("file:" + file.getAbsolutePath());
} catch (MalformedURLException e) {
return null;
}
}).filter(Objects::nonNull).collect(Collectors.toList());
URL[] jarArr = new URL[jars.size()];
jarArr = jars.toArray(jarArr);
URLClassLoader classLoader = new URLClassLoader(jarArr);
ServiceLoader<SayHello> loads = ServiceLoader.load(SayHello.class, classLoader);
loads.forEach(e -> {
System.out.println(e.execute("23456tygh"));
});
System.out.println("finish");
控制台打印
23456tygh
优美的中国话
hello, welcom to en,23456tygh
finish
因此一个简单插件加载工具方法就出来了
/**
* 加载文件系统中某个目录下接口实现对应的jar包
*
* @param jarDir
* @param clazz
* @return
* @param <T>
*/
public static <T> ServiceLoader<T> loadByFile(String jarDir, Class<T> clazz) {
File dir = new File(jarDir);
if (!dir.exists() || !dir.isDirectory()) {
return ServiceLoader.load(clazz);
}
List<URL> jars = Arrays.stream(dir.listFiles()).filter(file -> file.getName().endsWith("jar")).map(file -> {
try {
return new URL("file:" + file.getAbsolutePath());
} catch (MalformedURLException e) {
return null;
}
}).filter(Objects::nonNull).collect(Collectors.toList());
URL[] jarArr = new URL[jars.size()];
jarArr = jars.toArray(jarArr);
URLClassLoader classLoader = new URLClassLoader(jarArr);
return ServiceLoader.load(clazz, classLoader);
}