2.1 jdk-spi的实现原理
dubbo-spi是在jdk-spi的基础上进行重写优化,下面看一下jdk-spi。
一、作用
- 为接口自动寻找实现类。
二、实现方式
- 标准制定者制定接口
- 不同厂商编写针对于该接口的实现类,并在jar的“classpath:META-INF/services/全接口名称”文件中指定相应的实现类全类名
- 开发者直接引入相应的jar,就可以实现为接口自动寻找实现类的功能
三、使用方法

注意:示例以Log体系为例,但是实际中的Log体系并不是这样来实现的。
1、pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.hulk</groupId>
<artifactId>java-spi</artifactId>
<version>1.0-SNAPSHOT</version>
</project>
2、标准接口:com.hulk.javaspi.Log
package com.hulk.javaspi;
public interface Log {
void execute();
}
3、具体实现1:com.hulk.javaspi.Log4j
package com.hulk.javaspi;
public class Log4j implements Log {
@Override
public void execute() {
System.out.println("log4j ...");
}
}
4、具体实现2:com.hulk.javaspi.Logback
package com.hulk.javaspi;
public class Logback implements Log {
@Override
public void execute() {
System.out.println("logback ...");
}
}
5、指定使用的实现文件:META-INF/services/com.hulk.javaspi.Log
1 com.hulk.javaspi.Logback
注意
- 这里指定了实现类Logback,那么加载的时候就会自动为Log接口指定实现类为Logback。
- 这里也可以指定两个实现类,那么在实际中使用哪一个实现类,就需要使用额外的手段来控制。
1 com.hulk.javaspi.Logback
2 com.hulk.javaspi.Log4j
6、加载实现主类:com.hulk.javaspi.Main
package com.hulk.javaspi; import java.util.Iterator;
import java.util.ServiceLoader; public class Main {
public static void main(String[] args) {
ServiceLoader<Log> serviceLoader = ServiceLoader.load(Log.class);
Iterator<Log> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
Log log = iterator.next();
log.execute();
}
}
}
注意:
- ServiceLoader不是实例化以后,就去读取配置文件中的具体实现,并进行实例化。而是等到使用迭代器去遍历的时候,才会加载对应的配置文件去解析,调用hasNext方法的时候会去加载配置文件进行解析,调用next方法的时候进行实例化并缓存 - 具体见“源码分析”
现在来解析Main的源码。
四、源码解析
1、获取ServiceLoader
ServiceLoader<Log> serviceLoader = ServiceLoader.load(Log.class);
源码:
首先来看一下ServiceLoader的6个属性
private static final String PREFIX = "META-INF/services/";//定义实现类的接口文件所在的目录
private final Class<S> service;//接口
private final ClassLoader loader;//定位、加载、实例化实现类
private final AccessControlContext acc;//权限控制上下文
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();//以初始化的顺序缓存<接口全名称, 实现类实例>
private LazyIterator lookupIterator;//真正进行迭代的迭代器
其中LazyIterator是ServiceLoader的一个内部类,在迭代部分会说。
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);
}
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();
}
public void reload() {
providers.clear();//清空缓存
lookupIterator = new LazyIterator(service, loader);
}
这样一个ServiceLoader实例就创建成功了。在创建的过程中,我们看到还实例化了一个LazyIterator,该类下边会说。
2、获取迭代器并迭代
Iterator<Log> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
Log log = iterator.next();
log.execute();
}
外层迭代器:
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();
}
};
}
从查找过程hasNext()和迭代过程next()来看。
- hasNext():先从provider(缓存)中查找,如果有,直接返回true;如果没有,通过LazyIterator来进行查找。
- next():先从provider(缓存)中直接获取,如果有,直接返回实现类对象实例;如果没有,通过LazyIterator来进行获取。
下面来看一下,LazyIterator这个类。首先看一下他的属性:
Class<S> service;//接口
ClassLoader loader;//类加载器
Enumeration<URL> configs = null;//存放配置文件
Iterator<String> pending = null;//存放配置文件中的内容,并存储为ArrayList,即存储多个实现类名称
String nextName = null;//当前处理的实现类名称
其中,service和loader在上述实例化ServiceLoader的时候就已经实例化好了。
下面看一下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);
}
}
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);
} 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;
}
hasNextService()中,核心实现如下:
- 首先使用loader加载配置文件,此时找到了META-INF/services/com.hulk.javaspi.Log文件;
- 然后解析这个配置文件,并将各个实现类名称存储在pending的ArrayList中; --> 此时[ com.hulk.javaspi.Logback ]
- 最后指定nextName; --> 此时nextName=com.hulk.javaspi.Logback
下面看一下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);
}
}
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
}
nextService()中,核心实现如下:
- 首先加载nextName代表的类Class,这里为com.hulk.javaspi.Logback;
- 之后创建该类的实例,并转型为所需的接口类型
- 最后存储在provider中,供后续查找,最后返回转型后的实现类实例。
再next()之后,拿到实现类实例后,就可以执行其具体的方法了。
五、缺点
- 查找一个具体的实现需要遍历查找,耗时;-->此时就体现出Collection相较于Map差的地方,map可以直接根据key来获取具体的实现 (dubbo-spi实现了根据key获取具体实现的方式)
2.1 jdk-spi的实现原理的更多相关文章
- 解析JDK动态代理实现原理
JDK动态代理使用实例 代理模式的类图如上.关于静态代理的示例网上有很多,在这里就不讲了. 因为本篇讲述要点是JDK动态代理的实现原理,直接从JDK动态代理实例开始. 首先是Subject接口类. p ...
- 最近学习了JDK SPI
JDK SPI是什么 最近工作中听几个同事说了好几次SPI这个名词,虽然和我没关系,但是心里默默想还是学习一下,不然下次和我说到SPI,连是什么都不知道那就尴尬了. 所以SPI是什么呢?SPI全称Se ...
- JDK SPI
最近学习了JDK SPI JDK SPI是什么 最近工作中听几个同事说了好几次SPI这个名词,虽然和我没关系,但是心里默默想还是学习一下,不然下次和我说到SPI,连是什么都不知道那就尴尬了. 所以 ...
- NIO 源码分析(04) 从 SelectorProvider 看 JDK SPI 机制
目录 一.SelectorProvider SPI 二.SelectorProvider 加载过程 2.1 SelectorProvider 加载 2.2 Windows 下 DefaultSelec ...
- JDK SPI 机制
一.概述 最早看到 SPI 这个机制是在 dubbo 实现 中,最近发现原来也不是什么新东西,竟然就是 JDK 中内置的玩意,今天就来一探究竟,看看它到底是什么玩意! SPI的全称是 Service ...
- 基于JDK的动态代理原理分析
基于JDK的动态代理原理分析 这篇文章解决三个问题: What 动态代理是什么 How 动态代理怎么用 Why 动态代理的原理 动态代理是什么? 动态代理是代理模式的一种具体实现,是指在程序运行期间, ...
- 记一次 JDK SPI 配置不生效的问题 → 这么简单都不会,还是回家养猪吧
开心一刻 今天去幼儿园接小侄女,路上聊起了天 小侄女:小叔,今天我吃东西被老师发现了 我:老师说了什么 小侄女:她说拿出来,跟小朋友一起分享 我:那你拿出来了吗 小侄女一脸可怜的看向我,说道:没有,我 ...
- JDK动态代理实现原理
之前虽然会用JDK的动态代理,但是有些问题却一直没有搞明白.比如说:InvocationHandler的invoke方法是由谁来调用的,代理对象是怎么生成的.直到看了他的文章才彻底明白,附网址:htt ...
- JDK 动态代理实现原理
一.引言 Java动态代理机制的出现,使得Java开发人员不用手工编写代理类,只要简单地指定一组接口及委托类对象便能动态生成代理类.代理类会负责将所有方法的调用分派到委托对象上反射执行,在分派执行的过 ...
- JDK动态代理实现原理--转载
之前虽然会用JDK的动态代理,但是有些问题却一直没有搞明白.比如说:InvocationHandler的invoke方法是由谁来调用的,代理对象是怎么生成的,直到前几个星期才把这些问题全部搞明白了. ...
随机推荐
- 跟厂长学PHP7内核(六):变量之zval
记得网上流传甚广的段子"PHP是世界上最好的语言",暂且不去讨论是否言过其实,但至少PHP确实有独特优势的,比如它的弱类型,即只需要$符号即可声明变量,使得PHP入手门槛极低,成为 ...
- React Native Android启动白屏的一种解决方案上
我们用RN去开发Android应用的时候,我们会发现一个很明显的问题,这个问题就是启动时每次都会有1~3秒的白屏时间,直到项目加载出来 为什么会出现这个问题? RN开发的应用在启动时,首先会将js b ...
- MySQL DROP DB或TABLE场景下借助SQL Thread快速应用binlog恢复方案
[问题] 假设有这种场景,误操作DROP DB或TABLE,常规的恢复操作是还原全备份,并用mysqlbinlog追加到drop操作前的位置. 如果需要恢复的binlog的日志量比较大而我们只希望恢复 ...
- Oracle中append与Nologging
快速向表中插入大量数据Oracle中append与Nologging 2017-05-05 / VIEWS: 304 来源于:http://blog.sina.com.cn/s/blog_61cd89 ...
- 【dfs】BZOJ1703-[Usaco2007 Mar]Ranking the Cows 奶牛排名
[题目大意] 农夫约翰有N(1≤N≤1000)头奶牛,每一头奶牛都有一个确定的独一无二的正整数产奶率.约翰想要让这些奶牛按产奶率从高到低排序,约翰已经比较了M(1≤M≤10000)对奶牛的产奶率,但他 ...
- HDU 5836 Rubik's Cube BFS
Rubik's Cube 题目连接: http://acm.hdu.edu.cn/showproblem.php?pid=5836 Description As we all know, Zhu is ...
- UVALive 6911 Double Swords 树状数组
Double Swords 题目连接: https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8 ...
- JVM7、8参数详解及优化
1. JVM堆内存划分 这两天看到下面这篇文章的图不错. 一图读懂JVM架构解析 1.1 JDK7及以前的版本 其中最上一层是Nursery内存,一个对象被创建以后首先被放到Nursery中的Eden ...
- Linux——多线程下解决生产消费者模型
我们学习了操作系统,想必对生产消费者问题都不陌生.作为同步互斥问题的一个经典案例,生产消费者模型其实是解决实际问题的基础模型,解决很多的实际问题都会依赖于它.而此模型要解决最大的问题便是同步与互斥.而 ...
- svn简单记录
记录一下工作中常用到的svn命令 一.文件的提交流程 1.svn up // 先更新本地文件 2.svn st // svn status 查看要提交的文件 3.#svn ci -m &quo ...