Java SPI机制学习笔记
最近在阅读框架源代码时,常常看到 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<PrefixMatcher> matcher = ServiceLoader.load(PrefixMatcher.class);
Iterator<PrefixMatcher> 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 <T> List<T> load(Class<T> cls) {
List<String> implClasses = readServiceFile(cls);
List<T> implList = new ArrayList<T>();
for (String implClass : implClasses) {
Class<T> c = null;
try {
c = (Class<T>) Class.forName(implClass);
implList.add(c.newInstance());
} catch (Exception e) {
return new ArrayList<T>();
}
}
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<PrefixMatcher> implList = load(PrefixMatcher.class);
if (implList != null && implList.size() >0) {
for (PrefixMatcher matcher: implList) {
System.out.println(matcher.obtainMatchedWords("sh"));
}
}
}
}

ServiceLoader 的实现涉及到如下概念: 指向对象类型的 Class<S> 对象; 类加载器 ClassLoader; 服务实现类的资源抽象; 服务实现类的全名字符串。结合类加载器和资源抽象获得服务实现类的全名字符串,再通过类加载器获取 Class<S> 对象, 最后通过 Class<S> 对象来构造服务实现类 S 的实例 s 。
ServiceLoader 的成员为 <Class<S> service, ClassLoader loader, LinkedHashMap<String,S> providers, LazyIterator lookupIterator>, 其中 service 是服务接口,loader 是类加载器, providers 是服务实现类的缓存, lookupIterator 是获取服务实现类的迭代器,是 ServiceLoader 的内部类。 LazyIterator 的成员是 <Class<S> service, ClassLoader loader, Enumeration<URL> configs, Iterator<String> pending, String nextName>, configs 存放服务实现类的资源配置抽象, pending 存放服务实现类的全名字符串, nextName 是下一个可获取的服务实现类的全名字符串。加载资源使用到 classLoader 的 getSystemResources 和 getResources 方法。Java里的资源抽象使用类 URL 来唯一标识,无论是本地文件 ( file:/// ) 还是网络文件 (http(s):// )。由于要从文件或网络读取文本字符串,因此要使用 BufferedReader 。
在 Java 中,Class<T> 和 ClassLoader 是造物之始。万物皆是“某类T” 的存在物,而“某类T” 是“万类之类 Class<T>” 的存在物,类别也是一种存在物,存在物即 Object。实例 t -> 类别 T -> 所有类别的抽象 Class<T> -> Object。要创造类别 T 的实例,先通过某种方式(ClassLoader)找到该物的“种子”(Class<T> 对象),然后通过该种子来创造具体的物 t。要生成一个 Integer 对象,先找到 Class<Integer> , 然后 newInstance 出 Integer 的实例。而造物也要有个规则,“女娲造物”和“凡人造物”,如果要造一模一样的物种,必须先经由女娲造物,否则就会造成混乱(至少软件中会出现问题)。在 Java 里就有 BootstrapClassLoader -> ExtClassLoader -> AppClassLoader -> CustomClassLoader 的先后规则。关于类加载器可参见 【《Java类加载器总结》,《深入探讨Java类加载器》】, 阅读一个 ClassLoader 的实现。
Java SPI机制学习笔记的更多相关文章
- java反射机制学习笔记
内容引用自:https://www.cnblogs.com/wkrbky/p/6201098.html https://www.cnblogs.com/xumBlog/p/8882489.html,本 ...
- JAVA 类加载机制学习笔记
JAVA 类生命周期 如上图所示,Java类的生命周期如图所示,分别为加载.验证.准备.解析.初始化.使用.卸载.其中验证.准备.解析这三个步骤统称为链接. 加载:JVM根据全限定名来获取一段二进制字 ...
- JAVA的反射机制学习笔记(二)
上次写JAVA的反射机制学习笔记(一)的时候,还是7月22号,这些天就瞎忙活了.自己的步伐全然被打乱了~不能继续被动下去.得又一次找到自己的节奏. 4.获取类的Constructor 通过反射机制得到 ...
- java之jvm学习笔记六-十二(实践写自己的安全管理器)(jar包的代码认证和签名) (实践对jar包的代码签名) (策略文件)(策略和保护域) (访问控制器) (访问控制器的栈校验机制) (jvm基本结构)
java之jvm学习笔记六(实践写自己的安全管理器) 安全管理器SecurityManager里设计的内容实在是非常的庞大,它的核心方法就是checkPerssiom这个方法里又调用 AccessCo ...
- JAVA GUI编程学习笔记目录
2014年暑假JAVA GUI编程学习笔记目录 1.JAVA之GUI编程概述 2.JAVA之GUI编程布局 3.JAVA之GUI编程Frame窗口 4.JAVA之GUI编程事件监听机制 5.JAVA之 ...
- JUC.Lock(锁机制)学习笔记[附详细源码解析]
锁机制学习笔记 目录: CAS的意义 锁的一些基本原理 ReentrantLock的相关代码结构 两个重要的状态 I.AQS的state(int类型,32位) II.Node的waitStatus 获 ...
- Java多线程技术学习笔记(二)
目录: 线程间的通信示例 等待唤醒机制 等待唤醒机制的优化 线程间通信经典问题:多生产者多消费者问题 多生产多消费问题的解决 JDK1.5之后的新加锁方式 多生产多消费问题的新解决办法 sleep和w ...
- 《深入理解Java虚拟机》学习笔记
<深入理解Java虚拟机>学习笔记 一.走近Java JDK(Java Development Kit):包含Java程序设计语言,Java虚拟机,JavaAPI,是用于支持 Java 程 ...
- 组件化框架设计之Java SPI机制(三)
阿里P7移动互联网架构师进阶视频(每日更新中)免费学习请点击:https://space.bilibili.com/474380680 本篇文章将从深入理解java SPI机制来介绍组件化框架设计: ...
随机推荐
- java开发之——[接口回调]
一.回调的含义和用途 1. 什么是回调? 一般来说,模块之间都存在一定的调用关系,从调用方式上看,可以分为三类:同步调用.异步调用和回调.同步调用是一种阻塞式调用,即在函数A的函数体里通过书写函数B的 ...
- Java7编程高级进阶学习笔记
本书PDF 下载地址: http://pan.baidu.com/s/1c141KGS 密码:v6i1 注:本文有空会跟新: 讲述的是jdk7的内容: 注关于java 更详细的内容请进入:<Ja ...
- Win7 x64 svn 服务器搭建
SVN服务器搭建和使用 Subversion是优秀的版本控制工具,其具体的的优点和详细介绍,这里就不再多说. 首先来下载和搭建SVN服务器. 现在Subversion已经迁移到apache网站上了 ...
- LeetCode(36): 有效的数独
Medium! 题目描述: 判断一个 9x9 的数独是否有效.只需要根据以下规则,验证已经填入的数字是否有效即可. 数字 1-9 在每一行只能出现一次. 数字 1-9 在每一列只能出现一次. 数字 1 ...
- Lavarel - 模块间复用代码
代码复用在项目中早晚会遇到,这不在用 Laravel 给博客增加 Feed 订阅功能 就到了需要将生成网页 description 的函数提取出来,在文章显示与 Feed 生成的两个 Controll ...
- PHP对Url中的汉字进行编码和解码
有的新手朋友们对于url编码解码这个概念,或许有点陌生.但是如果这么说,当我们在浏览各大网页时,可能发现有的url里有一些特殊符号比如#号,&号,_号或者汉字等等,那么为了符合url的规范,存 ...
- 性能测试二十六:环境部署之Mysql+Redis+Tomcat环境整合
系统中使用了缓存+数据库,通用读取数据规则1.先从缓存读数据,如果有,直接返回数据:2.如果没有,去数据库中读,然后再插入到缓存中,再返回数据 Mysql+Redis+Tomcat环境整合 1.修改P ...
- HTTP常见响应状态码
200 : (OK) 服务器已成功处理了请求. 通常,这表示服务器提供了请求的网页. 201 : (Created) 请求成功并且服务器创建了新的资源. 301 : (Moved Permanentl ...
- python+selenium+unittest 实现自动化测试
示例代码: baidu.py import csv #导入csv模块 from itertools import islice #从itertools导入islice,后边让其默认跳过第一行使用 fr ...
- python 全栈开发,Day64(视图,触发器,函数,存储过程,事务)
昨日内容回顾 pymysql:属于python的一个模块 pip3 install pymysql conn = pymysql.connect(...,charset = 'uft8') 创建游标 ...