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
1package com.hulk.javaspi;23publicinterface Log {4void execute();5}
3、具体实现1:com.hulk.javaspi.Log4j

1package com.hulk.javaspi;23publicclassLog4jimplements Log {4 @Override5publicvoid execute() {6System.out.println("log4j ...");7 }8}

4、具体实现2:com.hulk.javaspi.Logback

1package com.hulk.javaspi;23publicclassLogbackimplements Log {4 @Override5publicvoid execute() {6System.out.println("logback ...");7 }8}

5、指定使用的实现文件:META-INF/services/com.hulk.javaspi.Log
1com.hulk.javaspi.Logback
注意
这里指定了实现类Logback,那么加载的时候就会自动为Log接口指定实现类为Logback。
这里也可以指定两个实现类,那么在实际中使用哪一个实现类,就需要使用额外的手段来控制。
1com.hulk.javaspi.Logback
2 com.hulk.javaspi.Log4j
6、加载实现主类:com.hulk.javaspi.Main

1package com.hulk.javaspi; 2 3import java.util.Iterator; 4import java.util.ServiceLoader; 5 6publicclass Main { 7publicstaticvoid main(String[] args) { 8ServiceLoader serviceLoader = ServiceLoader.load(Log.class); 9Iterator iterator = serviceLoader.iterator();10while (iterator.hasNext()) {11Log log = iterator.next();12 log.execute();13 }14 }15}

注意:
ServiceLoader不是实例化以后,就去读取配置文件中的具体实现,并进行实例化。而是等到使用迭代器去遍历的时候,才会加载对应的配置文件去解析,调用hasNext方法的时候会去加载配置文件进行解析,调用next方法的时候进行实例化并缓存- 具体见“源码分析”
现在来解析Main的源码。
四、源码解析
1、获取ServiceLoader
1ServiceLoader serviceLoader = ServiceLoader.load(Log.class);
源码:
首先来看一下ServiceLoader的6个属性

1privatestaticfinalString PREFIX = "META-INF/services/";//定义实现类的接口文件所在的目录2privatefinalClass service;//接口3privatefinalClassLoader loader;//定位、加载、实例化实现类4privatefinalAccessControlContext acc;//权限控制上下文5privateLinkedHashMap providers =newLinkedHashMap<>();//以初始化的顺序缓存<接口全名称, 实现类实例>6privateLazyIterator lookupIterator;//真正进行迭代的迭代器

其中LazyIterator是ServiceLoader的一个内部类,在迭代部分会说。

1publicstatic ServiceLoader load(Class service) { 2ClassLoader cl = Thread.currentThread().getContextClassLoader(); 3return ServiceLoader.load(service, cl); 4 } 5 6publicstatic ServiceLoader load(Class service, 7 ClassLoader loader) { 8returnnewServiceLoader<>(service, loader); 9 }1011privateServiceLoader(Class svc, ClassLoader cl) {12service = Objects.requireNonNull(svc, "Service interface cannot be null");13loader = (cl ==null) ? ClassLoader.getSystemClassLoader() : cl;14acc = (System.getSecurityManager() !=null) ? AccessController.getContext() :null;15 reload();16 }1718publicvoid reload() {19 providers.clear();//清空缓存20lookupIterator =new LazyIterator(service, loader);21}

这样一个ServiceLoader实例就创建成功了。在创建的过程中,我们看到还实例化了一个LazyIterator,该类下边会说。
2、获取迭代器并迭代
1Iterator iterator = serviceLoader.iterator();2while (iterator.hasNext()) {3Log log = iterator.next();4 log.execute();5}
外层迭代器:

1publicIterator iterator() { 2returnnewIterator() { 3 4Iterator> knownProviders 5= providers.entrySet().iterator(); 6 7publicboolean hasNext() { 8if (knownProviders.hasNext()) 9returntrue;10return lookupIterator.hasNext();11 }1213public S next() {14if (knownProviders.hasNext())15return knownProviders.next().getValue();16return lookupIterator.next();17 }1819publicvoid remove() {20thrownew UnsupportedOperationException();21 }2223 };24}

从查找过程hasNext()和迭代过程next()来看。
hasNext():先从provider(缓存)中查找,如果有,直接返回true;如果没有,通过LazyIterator来进行查找。
next():先从provider(缓存)中直接获取,如果有,直接返回实现类对象实例;如果没有,通过LazyIterator来进行获取。
下面来看一下,LazyIterator这个类。首先看一下他的属性:
1Class service;//接口2ClassLoader loader;//类加载器3Enumeration configs =null;//存放配置文件4Iterator pending =null;//存放配置文件中的内容,并存储为ArrayList,即存储多个实现类名称5String nextName =null;//当前处理的实现类名称
其中,service和loader在上述实例化ServiceLoader的时候就已经实例化好了。
下面看一下hasNext():

1publicboolean hasNext() { 2if(acc ==null) { 3return hasNextService(); 4}else { 5PrivilegedAction action =newPrivilegedAction() { 6publicBoolean run() {return hasNextService(); } 7 }; 8return AccessController.doPrivileged(action, acc); 9 }10 }1112privateboolean hasNextService() {13if(nextName !=null) {14returntrue;15 }16if(configs ==null) {17try {18String fullName = PREFIX + service.getName();19if(loader ==null)20configs = ClassLoader.getSystemResources(fullName);21else22configs = loader.getResources(fullName);23}catch (IOException x) {24fail(service, "Error locating configuration files", x);25 }26 }27while((pending ==null) || !pending.hasNext()) {28if(!configs.hasMoreElements()) {29returnfalse;30 }31pending = parse(service, configs.nextElement());32 }33nextName = pending.next();34returntrue;35}

hasNextService()中,核心实现如下:
首先使用loader加载配置文件,此时找到了META-INF/services/com.hulk.javaspi.Log文件;
然后解析这个配置文件,并将各个实现类名称存储在pending的ArrayList中; --> 此时[ com.hulk.javaspi.Logback ]
最后指定nextName; --> 此时nextName=com.hulk.javaspi.Logback
下面看一下next():

1public S next() { 2if(acc ==null) { 3return nextService(); 4}else { 5PrivilegedAction action =newPrivilegedAction() { 6publicS run() {return nextService(); } 7 }; 8return AccessController.doPrivileged(action, acc); 9 }10 }1112private S nextService() {13if(!hasNextService())14thrownew NoSuchElementException();15String cn = nextName;16nextName =null;17Class c =null;18try {19c = Class.forName(cn,false, loader);20}catch (ClassNotFoundException x) {21 fail(service,22"Provider " + cn + " not found");23 }24if(!service.isAssignableFrom(c)) {25 fail(service,26"Provider " + cn + " not a subtype");27 }28try {29S p = service.cast(c.newInstance());30 providers.put(cn, p);31return p;32}catch (Throwable x) {33 fail(service,34"Provider " + cn + " could not be instantiated",35 x);36 }37thrownewError();// This cannot happen38}

nextService()中,核心实现如下:
首先加载nextName代表的类Class,这里为com.hulk.javaspi.Logback;
之后创建该类的实例,并转型为所需的接口类型
最后存储在provider中,供后续查找,最后返回转型后的实现类实例。
再next()之后,拿到实现类实例后,就可以执行其具体的方法了。
五、缺点
查找一个具体的实现需要遍历查找,耗时;-->此时就体现出Collection相较于Map差的地方,map可以直接根据key来获取具体的实现 (dubbo-spi实现了根据key获取具体实现的方式)
网友评论