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获取具体实现的方式)

最新文章

  1. OpenGL官方教程——着色器语言概述
  2. 在Oracle Linux上使用DTrace的相关指导
  3. Nginx HTTP负载均衡和反向代理配置
  4. iOS 学习笔记 六 (2015.03.28)常见错误
  5. [Flex] ButtonBar系列——flex3 labelFunction用户提供的函数,在每个项目上运行以确定其标签
  6. wap开发使用jquery mobile之后页面不加载外部css样式文件/js文件
  7. HUSTOJ(转发)
  8. 在ef下使用lambda实现left join
  9. pat_1014
  10. How to install MP4box on CentOS 6
  11. HTML5音频
  12. 第03讲- 第一个Android项目
  13. Unity打包android的apk与数据包.obb分离和apk签名
  14. mysql中utf8和utf8mb4区别
  15. Vue项目使用bootstrap
  16. C# 准确获取系统 CPU 使用率
  17. Java高级特性 第4节 输入输出流
  18. &lt;线程池-定时任务&gt; ScheduledExecutorService之shutdown引发的RejectedExecutionException问题
  19. leetcode1019
  20. 【Go命令教程】2. go build

热门文章

  1. 好用的在线HTTP接口测试 - HTTP GET/POST模拟请求测试工具-ApiPost
  2. 容器(Container)Frames和Panels
  3. Windows7双系统的启动顺序怎样修改?
  4. Western Subregional of NEERC, Minsk, Wednesday, November 4, 2015 Problem A. A + B
  5. 让Win2008+IIS7+ASP.NET支持10万并发请求
  6. LINUX 内核守护进程
  7. Revit API单位转换类
  8. [web.config]如何灵活使用配置文件
  9. Flume 1.5.0简单部署试用
  10. Android倒计时功能的实现