JavaSPI 机制学习笔记
最近在阅读框架源代码时,常常看到 SPI 的子包, 忍不住查了下:Service Provider Interface : 服务提供接口。
JavaSPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。具体而言:
STEP1. 定义一组接口, 假设是 autocomplete.PrefixMatcher;
STEP2. 写出接口的一个或多个实现(autocomplete.EffectiveWordMatcher, autocomplete.SimpleWordMatcher);
STEP3. 在 src/main/resources/ 下建立 /META-INF/services 目录, 新增一个以接口命名的文件 autocomplete.PrefixMatcher, 内容是要应用的实现类(autocomplete.EffectiveWordMatcher 或 autocomplete.SimpleWordMatcher 或两者);
STEP4. 使用 ServiceLoader 来加载配置文件中指定的实现。
SPI 的应用之一是可替换的插件机制。比如查看 JDBC 数据库驱动包,mysql-connector-java-5.1.18.jar 就有一个 /META-INF/services/java.sql.Driver 里面内容是 com.mysql.jdbc.Driver 。
代码示例:
1. 编写接口和实现类:autocomplete.PrefixMatcher, autocomplete.EffectiveWordMatcher, autocomplete.SimpleWordMatcher 见 《输入自动提示与补全功能的设计与实现》;
2. 在 src/main/resources/ 下建立文件 /META-INF/services/ autocomplete.PrefixMatcher 填入上述两个类之一或两者都填;
3. 编写测试类。
package autocomplete;
import java.util.Iterator;
import java.util.ServiceLoader;
/**
* Created by lovesqcc on 16-2-29.
*/
public class PrefixMatcherTest {
public static void main(String[] args) {
ServiceLoader matcher = ServiceLoader.load(PrefixMatcher.class);
Iterator matcherIter = matcher.iterator();
while (matcherIter.hasNext()) {
PrefixMatcher wordMatcher = matcherIter.next();
System.out.println(wordMatcher.getClass().getName());
String[] prefixes = new String[] {"a", "b", "c", "d", "e", "f", "g", "i",
"l", "n", "p", "r", "s", "t", "v", "w", "do", "finally"};
for (String prefix: prefixes) {
System.out.println(wordMatcher.obtainMatchedWords(prefix));
}
}
}
}
要写个 ServiceLoader 的简单实现也不难:1.
读取配置文件,获取实现类的全名称字符串;2. 使用 Java 反射机制来构造服务实现类的实例。可以使用泛型方法,避免获取的时候做类型转换。不过
JDK 自带的 java.util.ServiceLoader 实现得更加严谨一些,使用了 ClassLoader
来加载类,并使用迭代器来获取服务实现类。思路大体相同。
package autocomplete;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* Created by lovesqcc on 16-2-29.
* A very Simple JavaSPI implementation using java reflection
*/
public class SimpleServiceLoader {
private static final String PREFIX = "/META-INF/services/";
public static List load(Class cls) {
List<String> implClasses = readServiceFile(cls);
List implList = new ArrayList();
for (String implClass : implClasses) {
Class c = null;
try {
c = (Class) Class.forName(implClass);
implList.add(c.newInstance());
} catch (Exception e) {
return new ArrayList();
}
}
return implList;
}
private static List<String> readServiceFile(Class> cls) {
String infName = cls.getCanonicalName();
String fileName = cls.getResource(PREFIX+infName).getPath();
try {
BufferedReader br = new BufferedReader(new FileReader(new File(fileName)));
String line = "";
List<String> implClasses = new ArrayList<String>();
while ((line = br.readLine()) != null) {
implClasses.add(line);
}
return implClasses;
} catch (FileNotFoundException fnfe) {
System.out.println("File not found: " + fileName);
return new ArrayList<String>();
} catch (IOException ioe) {
System.out.println("Read file failed: " + fileName);
return new ArrayList<String>();
}
}
public static void main(String[] args) {
List implList = load(PrefixMatcher.class);
if (implList != null && implList.size() >0) {
for (PrefixMatcher matcher: implList) {
System.out.println(matcher.obtainMatchedWords("sh"));
}
}
}
}
ServiceLoader 的实现涉及到如下概念:指向对象类型的
Class 对象;类加载器 ClassLoader;服务实现类的资源抽象;服务实现类的全名字符串。结合类加载器和资源抽象获得服务实现类的全名字符串,再通过类加载器获取 Class 对象, 最后通过
Class 对象来构造服务实现类 S 的实例 s 。
ServiceLoader 的实现主要包括:
1. 内部成员包含 2. LazyIterator 的内部成员包括 在 Java 中,Class 关注GitHub今日热榜,专注挖掘好用的开发工具,致力于分享优质高效的工具、资源、插件等,助力开发者成长! 点个在看 你最好看