2.1 jdk-spi的实现原理
Posted 赵计刚
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2.1 jdk-spi的实现原理相关的知识,希望对你有一定的参考价值。
dubbo-spi是在jdk-spi的基础上进行重写优化,下面看一下jdk-spi。
一、作用
- 为接口自动寻找实现类。
二、实现方式
- 标准制定者制定接口
- 不同厂商编写针对于该接口的实现类,并在jar的“classpath:META-INF/services/全接口名称”文件中指定相应的实现类全类名
- 开发者直接引入相应的jar,就可以实现为接口自动寻找实现类的功能
三、使用方法
注意:示例以Log体系为例,但是实际中的Log体系并不是这样来实现的。
1、pom.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 5 <modelVersion>4.0.0</modelVersion> 6 <groupId>com.hulk</groupId> 7 <artifactId>java-spi</artifactId> 8 <version>1.0-SNAPSHOT</version> 9 </project>
2、标准接口:com.hulk.javaspi.Log
1 package com.hulk.javaspi; 2 3 public interface Log { 4 void execute(); 5 }
3、具体实现1:com.hulk.javaspi.Log4j
1 package com.hulk.javaspi; 2 3 public class Log4j implements Log { 4 @Override 5 public void execute() { 6 System.out.println("log4j ..."); 7 } 8 }
4、具体实现2:com.hulk.javaspi.Logback
1 package com.hulk.javaspi; 2 3 public class Logback implements Log { 4 @Override 5 public void execute() { 6 System.out.println("logback ..."); 7 } 8 }
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
1 package com.hulk.javaspi; 2 3 import java.util.Iterator; 4 import java.util.ServiceLoader; 5 6 public class Main { 7 public static void main(String[] args) { 8 ServiceLoader<Log> serviceLoader = ServiceLoader.load(Log.class); 9 Iterator<Log> iterator = serviceLoader.iterator(); 10 while (iterator.hasNext()) { 11 Log log = iterator.next(); 12 log.execute(); 13 } 14 } 15 }
注意:
- ServiceLoader不是实例化以后,就去读取配置文件中的具体实现,并进行实例化。而是等到使用迭代器去遍历的时候,才会加载对应的配置文件去解析,调用hasNext方法的时候会去加载配置文件进行解析,调用next方法的时候进行实例化并缓存 - 具体见“源码分析”
现在来解析Main的源码。
四、源码解析
1、获取ServiceLoader
1 ServiceLoader<Log> serviceLoader = ServiceLoader.load(Log.class);
源码:
首先来看一下ServiceLoader的6个属性
1 private static final String PREFIX = "META-INF/services/";//定义实现类的接口文件所在的目录 2 private final Class<S> service;//接口 3 private final ClassLoader loader;//定位、加载、实例化实现类 4 private final AccessControlContext acc;//权限控制上下文 5 private LinkedHashMap<String,S> providers = new LinkedHashMap<>();//以初始化的顺序缓存<接口全名称, 实现类实例> 6 private LazyIterator lookupIterator;//真正进行迭代的迭代器
其中LazyIterator是ServiceLoader的一个内部类,在迭代部分会说。
1 public static <S> ServiceLoader<S> load(Class<S> service) { 2 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 3 return ServiceLoader.load(service, cl); 4 } 5 6 public static <S> ServiceLoader<S> load(Class<S> service, 7 ClassLoader loader) { 8 return new ServiceLoader<>(service, loader); 9 } 10 11 private ServiceLoader(Class<S> svc, ClassLoader cl) { 12 service = Objects.requireNonNull(svc, "Service interface cannot be null"); 13 loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; 14 acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null; 15 reload(); 16 } 17 18 public void reload() { 19 providers.clear();//清空缓存 20 lookupIterator = new LazyIterator(service, loader); 21 }
这样一个ServiceLoader实例就创建成功了。在创建的过程中,我们看到还实例化了一个LazyIterator,该类下边会说。
2、获取迭代器并迭代
1 Iterator<Log> iterator = serviceLoader.iterator(); 2 while (iterator.hasNext()) { 3 Log log = iterator.next(); 4 log.execute(); 5 }
外层迭代器:
1 public Iterator<S> iterator() { 2 return new Iterator<S>() { 3 4 Iterator<Map.Entry<String,S>> knownProviders 5 = providers.entrySet().iterator(); 6 7 public boolean hasNext() { 8 if (knownProviders.hasNext()) 9 return true; 10 return lookupIterator.hasNext(); 11 } 12 13 public S next() { 14 if (knownProviders.hasNext()) 15 return knownProviders.next().getValue(); 16 return lookupIterator.next(); 17 } 18 19 public void remove() { 20 throw new UnsupportedOperationException(); 21 } 22 23 }; 24 }
从查找过程hasNext()和迭代过程next()来看。
- hasNext():先从provider(缓存)中查找,如果有,直接返回true;如果没有,通过LazyIterator来进行查找。
- next():先从provider(缓存)中直接获取,如果有,直接返回实现类对象实例;如果没有,通过LazyIterator来进行获取。
下面来看一下,LazyIterator这个类。首先看一下他的属性:
1 Class<S> service;//接口 2 ClassLoader loader;//类加载器 3 Enumeration<URL> configs = null;//存放配置文件 4 Iterator<String> pending = null;//存放配置文件中的内容,并存储为ArrayList,即存储多个实现类名称 5 String nextName = null;//当前处理的实现类名称
其中,service和loader在上述实例化ServiceLoader的时候就已经实例化好了。
下面看一下hasNext():
1 public boolean hasNext() { 2 if (acc == null) { 3 return hasNextService(); 4 } else { 5 PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() { 6 public Boolean run() { return hasNextService(); } 7 }; 8 return AccessController.doPrivileged(action, acc); 9 } 10 } 11 12 private boolean hasNextService() { 13 if (nextName != null) { 14 return true; 15 } 16 if (configs == null) { 17 try { 18 String fullName = PREFIX + service.getName(); 19 if (loader == null) 20 configs = ClassLoader.getSystemResources(fullName); 21 else 22 configs = loader.getResources(fullName); 23 } catch (IOException x) { 24 fail(service, "Error locating configuration files", x); 25 } 26 } 27 while ((pending == null) || !pending.hasNext()) { 28 if (!configs.hasMoreElements()) { 29 return false; 30 } 31 pending = parse(service, configs.nextElement()); 32 } 33 nextName = pending.next(); 34 return true; 35 }
hasNextService()中,核心实现如下:
- 首先使用loader加载配置文件,此时找到了META-INF/services/com.hulk.javaspi.Log文件;
- 然后解析这个配置文件,并将各个实现类名称存储在pending的ArrayList中; --> 此时[ com.hulk.javaspi.Logback ]
- 最后指定nextName; --> 此时nextName=com.hulk.javaspi.Logback
下面看一下next():
1 public S next() { 2 if (acc == null) { 3 return nextService(); 4 } else { 5 PrivilegedAction<S> action = new PrivilegedAction<S>() { 6 public S run() { return nextService(); } 7 }; 8 return AccessController.doPrivileged(action, acc); 9 } 10 } 11 12 private S nextService() { 13 if (!hasNextService()) 14 throw new NoSuchElementException(); 15 String cn = nextName; 16 nextName = null; 17 Class<?> c = null; 18 try { 19 c = Class.forName(cn, false, loader); 20 } catch (ClassNotFoundException x) { 21 fail(service, 22 "Provider " + cn + " not found"); 23 } 24 if (!service.isAssignableFrom(c)) { 25 fail(service, 26 "Provider " + cn + " not a subtype"); 27 } 28 try { 29 S p = service.cast(c.newInstance()); 30 providers.put(cn, p); 31 return p; 32 } catch (Throwable x) { 33 fail(service, 34 "Provider " + cn + " could not be instantiated", 35 x); 36 } 37 throw new Error(); // This cannot happen 38 }
nextService()中,核心实现如下:
- 首先加载nextName代表的类Class,这里为com.hulk.javaspi.Logback;
- 之后创建该类的实例,并转型为所需的接口类型
- 最后存储在provider中,供后续查找,最后返回转型后的实现类实例。
再next()之后,拿到实现类实例后,就可以执行其具体的方法了。
五、缺点
- 查找一个具体的实现需要遍历查找,耗时;-->此时就体现出Collection相较于Map差的地方,map可以直接根据key来获取具体的实现 (dubbo-spi实现了根据key获取具体实现的方式)
以上是关于2.1 jdk-spi的实现原理的主要内容,如果未能解决你的问题,请参考以下文章