使用
-
定义接口
public interface IFunctionService { void test(); }
-
定义接口实现,这里实现了三个实现类。
public class FunctionService1 implements IFunctionService {
private static final String TAG = "FunctionService1";
@Override
public void test() {
Log.e(TAG, "test1: ");
}
}
public class FunctionService2 implements IFunctionService {
private static final String TAG = "FunctionService2";
@Override
public void test() {
Log.e(TAG, "test2: " );
}
}
public class FunctionService3 implements IFunctionService {
private static final String TAG = "StrategyService3";
@Override
public void test() {
Log.e(TAG, "test3: " );
}
}
-
编写配置文件,在src/main文件下创建resources/META-INF/services文件夹,然后在其中创建文件,文件名字是接口的全限定类名,内容是实现类的全限定类名,多个实现类用换行符分隔,如下:com.example.testspi.IFunctionService
注意:META-INF/services/文件路径不可以改变,这个在代码中写死的。
文件的内容如下:
com.example.testspi.FunctionService1
com.example.testspi.FunctionService2
com.example.testspi.FunctionService3
- 测试方法
public static void main(String[] args) {
ServiceLoader<IFunctionService> load = ServiceLoader.load(IFunctionService.class);
for (IFunctionService iFunctionService : load) {
iFunctionService.test();
}
}
概述
简介
服务提供者接口(Service Provider Interface,简写为SPI)是JDK内置的一种服务提供发现机制。可以用来加载框架扩展和替换组件,主要是被框架的开发人员使用。在java.util.ServiceLoader的文档里有比较详细的介绍。
Java中SPI机制主要思想是将装配的控制权移到程序之外,是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制,有点类似Spring的IOC机制。在模块化设计中这个机制尤其重要,其核心思想就是解耦。
规范约束
当服务的提供者,提供了服务接口的某种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。基于这样一个约定就能实现服务接口与实现的解耦。
缺点
-
不能按需加载,需要遍历所有的实现,并实例化,然后在循环中才能找到我们需要的实现。如果不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。
-
多个并发多线程使用ServiceLoader类的实例是不安全的。
-
扩展如果依赖其他的扩展,做不到自动注入和装配。
源码解析
今天按照方法的调用过程对源码进行分析。
- 先看一下ServiceLoader对应的全局变量
public final class ServiceLoader<S> implements Iterable<S> {
//接口配置文件对应的路径。
private static final String PREFIX = "META-INF/services/";
// 表示正在加载的服务的类或接口
private final Class<S> service;
// 用于定位、加载和实例化提供程序的类加载器
private final ClassLoader loader;
//创建ServiceLoader时采用的访问控制上下文
private LinkedHashMap<String, S> providers = new LinkedHashMap<>();
// 懒加载迭代器
private LazyIterator lookupIterator;
................
}
-
load()
为给定的服务类型创建一个新的服务加载器。
public static <S> ServiceLoader<S> load(Class<S> service) {
//获取类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
-
调用重载方法load
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) { return new ServiceLoader<>(service, loader); }
-
创建ClassLoader对象
private ServiceLoader(Class<S> svc, ClassLoader cl) { //其实就是svc service = Objects.requireNonNull(svc, "Service interface cannot be null"); loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl; reload(); }
-
调用reload
清除此加载器的实现类对象缓存,以便重新加载所有实现类对象。
public void reload() { providers.clear(); lookupIterator = new LazyIterator(service, loader); }
-
LayIterator创建
LazyIterator是一个迭代器,查找实现类和创建实现类的过程,都在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; }
-
迭代
当我们调用iterator.hasNext和iterator.next方法的时候,实际上调用的都是LazyIterator的相应方法。
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(); } }; }
-
hasNextService
private boolean hasNextService() { //第二次调用的时候,已经解析完成了,直接返回 if (nextName != null) { return true; } //如果读取过则不在重新读取 if (configs == null) { try { //配置文件路径,加上接口的全限定类名,就是文件服务类的文件 String fullName = PREFIX + service.getName(); //从路径中读取配置,将文件路径转成URL对象 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; } //解析URL文件对象,读取内容,最后返回 pending = parse(service, configs.nextElement()); } nextName = pending.next(); return true; }
-
nextService
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, // 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 { //强转。 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 }
总结
- 调用load方法后本质是读取配置文件,动态的加载配置文件中的实现类。
- 使用了懒加载的机制,只有在遍历寻找时才会创建对象。