SPI 全称为 (Service Provider Interface) ,是JDK内置的一种服务提供发现机制,能够轻松实现面向服务的注册与发现,实现服务提供与应用的解耦,并且能够实现动静加载

SPI 能做什么

利用SPI机制,sdk的开发者能够为使用者提供扩大点,使用者无需批改源码,有点相似Spring @ConditionalOnMissingBean 的意思

入手实现一个SPI

例如咱们要正在开发一个sdk其中有一个缓存的性能,然而用户很可能不想应用咱们的缓存实现,用户想要自定义缓存的实现,此时应用spi就十分的适合了

新建一个maven工程命名为sdk

Cache 接口

import java.util.ServiceLoader;public interface Cache {    String getName();    static Cache load() {        // ServiceLoader 实现了 Iterable,能够加载到Cache接口的多个实现类        ServiceLoader<Cache> cacheServiceLoader =  ServiceLoader.load(Cache.class);        return cacheServiceLoader.iterator().next();    }}
ServiceLoader 是Java提供服务发现工具类,这是咱们实现SPI的要害

CacheDefaultImpl

public class CacheDefaultImpl implements Cache {    public String getName() {        return "defaultImpl";    }}

除此之外,ServiceLoader 还须要在classpath:META-INF/services 下找到以该接口全名命名的文件,这里咱们间接在resource 目录下创立META-INF/services/ com.github.tavenyin.Cache文件即可,文件中指定Cache的实现类

# 此处能够指定多个实现类com.github.tavenyin.CacheDefaultImpl

Run

咱们建设一个新的maven子工程,并引入sdk模块,执行测试代码

System.out.println(Cache.load().getName()) # 输入后果为 defaultImpl

使用者定制化

那么如果sdk的使用者不想应用咱们的CacheDefaultImpl了怎么办,没关系使用者只须要笼罩 classpath:META-INF/services/com.github.tavenyin.Cache 就能够了 (使用者在同样在resource下创立即可笼罩)

咱们再来运行一下测试代码,输入后果为 newImpl

ServiceLoader 实现原理

ServiceLoader 的实现原理还是比较简单的,试想一下,如果咱们本人实现一个ServiceLoader,咱们会怎么做?

  1. 通过指定的文件加载出所有的类名
  2. 通过反射构建这些对象

没错,ServiceLoader 就是这么做的,咱们来简略看一下源码

入口 ServiceLoader::iterator::next

// Cached providers, in instantiation orderprivate LinkedHashMap<String,S> providers = new LinkedHashMap<>();// The current lazy-lookup iteratorprivate LazyIterator lookupIterator;   // ServiceLoader::iteratorpublic 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();        }        // ServiceLoader::iterator::next        public S next() {            if (knownProviders.hasNext())                return knownProviders.next().getValue();            return lookupIterator.next();        }        public void remove() {            throw new UnsupportedOperationException();        }    };}

从providers 初始为一个空的LinkedHashMap,咱们无需关注,所以knownProviders::hasNext 肯定返回false,咱们直奔knownProviders::next

knownProviders::next 中外围逻辑在nextService() 中

private S nextService() {    // hasNextService 中做了两件事    // 1. 判断是否还有服务的提供者    // 2. 通过 "META-INF/services/" + 接口全名 加载所有提供者ClassName    if (!hasNextService())        throw new NoSuchElementException();    String cn = nextName;    nextName = null;    Class<?> c = null;    try {        // 通过ClassName 创立Class        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}

与咱们上述剖析的实现过程统一,更多细节感兴趣的童鞋可自行浏览

ServiceLoader 如何实现动静加载

同一个 ServiceLoader 对象的话,不会从新加载META-INF/services/下的信息。如果咱们须要动静加载的话,能够思考每次从新创立新的ServiceLoader 对象,或者调用 ServiceLoader::reload

demo 地址

https://github.com/TavenYin/j...

如果感觉有播种,能够关注我的公众号【殷地理】,你的点赞和关注就是对我最大的反对