一、SPI机制简介
服务提供者接口(Service Provider Interface,简写为SPI)是JDK内置的一种服务提供发现机制。可以用来加载框架扩展和替换组件,主要是被框架的开发人员使用。在java.util.ServiceLoader的文档里有比较详细的介绍。
系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块的方案、xml解析模块、jdbc模块的方案等。面向对象的设计推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则:如果需要替换组建的一种实现,就需要修改框架的代码。SPI机制正是解决这个问题。
Java中SPI机制主要思想是将装配的控制权移到程序之外,是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制,有点类似Spring的IOC机制。在模块化设计中这个机制尤其重要,其核心思想就是解耦。
二、SPI具体约定
Java SPI的具体约定:当服务的提供者,提供了服务接口的某种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。基于这样一个约定就能实现服务接口与实现的解耦。
三、Java SPI机制的缺点
不能按需加载,需要遍历所有的实现,并实例化,然后在循环中才能找到我们需要的实现。如果不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。
多个并发多线程使用ServiceLoader类的实例是不安全的。
扩展如果依赖其他的扩展,做不到自动注入和装配。
四、回顾策略模式
策略模式文章参考:https://www.jianshu.com/p/f771021e2471
定义策略接口 IStrategyService
public interface IStrategyService {
public static final String TAG = "StrategyService";
void method();
}
定义多个实现类如下:
/**
* DESC : 策略实现类1
*/
public class StrategyImpl1 implements IStrategyService {
@Override
public void method() {
Log.d(TAG, "SpiImpl1==>method");
}
}
/**
* DESC : 策略实现类2
*/
public class StrategyImpl2 implements IStrategyService {
@Override
public void method() {
Log.d(TAG, "SpiImpl2==>method");
}
}
/**
* DESC : 策略实现类3
*/
public class StrategyImpl3 implements IStrategyService {
@Override
public void method() {
Log.d(TAG, "SpiImpl3==>method");
}
}
策略接口IStrategyService作为参数,根据传递不同的策略实现类,调用对用的方法
/**
* DESC : 策略包装类
*/
public class StrategyWrapper {
private IStrategyService strategyService;
public StrategyWrapper(IStrategyService strategyService) {
this.strategyService = strategyService;
}
public void testMethod() {
strategyService.method();
}
}
接下来对策略模式进行测试:
public class StrategyTest {
/**
* 测试策略模式
*/
public void testStrategyPattern() {
//调用策略1的方法
StrategyWrapper strategyWrapper1 = new StrategyWrapper( new StrategyImpl1());
strategyWrapper1.testMethod();
//调用策略2的方法
StrategyWrapper strategyWrapper2 = new StrategyWrapper( new StrategyImpl2());
strategyWrapper2.testMethod();
//调用策略3的方法
StrategyWrapper strategyWrapper3 = new StrategyWrapper( new StrategyImpl3());
strategyWrapper3.testMethod();
}
}
五、SPI 应用简单实现
SPI是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制,现就对策略模式进行改进。
首先在android studio工程下app/src/main/目录下创建resources/META-INF/services目录,并且创建策略接口IStrategyService 的全限定名文件,即com.example.nqct.spiservicetest.IStrategyService (自行修改)如下图:
然后把所有的策略实现类名加入到此文件中
com.example.nqct.spiservicetest.StrategyImpl1
com.example.nqct.spiservicetest.StrategyImpl2
com.example.nqct.spiservicetest.StrategyImpl3
SPI测试方法
public class StrategyTest {
/**
* SPI机制
*/
public void testSpiMethod() {
//通过ServiceLoader加载所有在com.example.nqct.spiservicetest.IStrategyService文件中的所有策略实现类
ServiceLoader<IStrategyService> loader = ServiceLoader.load(IStrategyService.class);
//对策略实现类进行遍历,调用其方法
Iterator<IStrategyService> iter = loader.iterator();
while(iter.hasNext()) {
IStrategyService strategyService = iter.next();
strategyService .method();
}
}
}
下面对ServiceLoader加载进行源码剖析:
5.1 调用load方法创建加载IStrategyService的加载器对象ServiceLoader
/**
* 调用load方法
*/
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;
// Android-changed: Do not use legacy security code.
// On Android, System.getSecurityManager() is always null.
// acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
- 2 接着调用reload()方法创建ServiceLoader中的内部类LazyIterator
/**
* 懒加载遍历器
*/
private class LazyIterator
implements Iterator<S>
{
Class<S> service;
ClassLoader loader;
Enumeration<URL> configs = null;
Iterator<String> pending = null;
String nextName = null;
private LazyIterator(Class<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
}
5.3 调用ServiceLoader的iterator()方法进行遍历
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();
}
};
}
5.4 调用LazyIterator的hasNext()方法,寻找配置文件com.example.nqct.spiservicetest.IStrategyService并读取
private static final String PREFIX = "META-INF/services/";
public boolean hasNext() {
return hasNextService();
}
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
//这个就是IStrategyService文件的路径,META-INF/services/com.example.nqct.spiservicetest.IStrategyService
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;
}
/**
* 调用parse方法,对com.example.nqct.spiservicetest.IStrategyService文件的内容一行行读取,就获取到所有的策略实现类名字
*/
private Iterator<String> parse(Class<?> service, URL u)
throws ServiceConfigurationError
{
InputStream in = null;
BufferedReader r = null;
ArrayList<String> names = new ArrayList<>();
try {
in = u.openStream();
r = new BufferedReader(new InputStreamReader(in, "utf-8"));
int lc = 1;
while ((lc = parseLine(service, u, r, lc, names)) >= 0);
} catch (IOException x) {
fail(service, "Error reading configuration file", x);
} finally {
try {
if (r != null) r.close();
if (in != null) in.close();
} catch (IOException y) {
fail(service, "Error closing configuration file", y);
}
}
return names.iterator();
}
5.5 调用LazyIterator的next()方法
public S next() {
return nextService();
}
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
//可以看到,此处是将得到的策略实现类进行反射获取class
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
// Android-changed: Let the ServiceConfigurationError have a cause.
"Provider " + cn + " not found", x);
// "Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
// Android-changed: Let the ServiceConfigurationError have a cause.
ClassCastException cce = new ClassCastException(
service.getCanonicalName() + " is not assignable from " + c.getCanonicalName());
fail(service,
"Provider " + cn + " not a subtype", cce);
// fail(service,
// "Provider " + cn + " not a subtype");
}
try {
//调用newInstance()方法创建实例对象,由此就获取到策略实现类的实例对象了
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
}
六、SPI机制总结
1.使用IO流读取策略接口配置文件
2.实现迭代器接口及懒加载的模式在遍历阶段创建对象
3.使用反射创建对象并放入到缓存中。