JAVA SPI = 基于接口的编程+策略模式+配置文件 的动态加载机制
Java是一种面向对象语言,虽然Java8开始支持函数式编程和Stream,但是总体来说,还是面向对象的语言。在使用Java进行面向对象开发时,一般会推荐使用基于接口的编程,程序的模块与模块之前不会直接进行实现类的硬编码。而在实际的开发过程中,往往一个接口会有多个实现类,各实现类要么实现的逻辑不同,要么使用的方式不同,还有的就是实现的技术不同。为了使调用方在调用接口的时候,明确的知道自己调用的是接口的哪个实现类,或者说为了实现在模块装配的时候不用在程序里动态指明,这就需要一种服务发现机制。Java中的SPI加载机制能够满足这样的需求,它能够自动寻找某个接口的实现类。
大量的框架使用了Java的SPI技术,如下:
(1)JDBC加载不同类型的数据库驱动
(2)日志门面接口实现类加载,SLF4J加载不同提供商的日志实现类
(3)Spring中大量使用了SPI
(4)Dubbo里面有很多个组件,每个组件在框架中都是以接口的形成抽象出来!具体的实现又分很多种,在程序执行时根据用户的配置来按需取接口的实现
当服务的提供者,提供了接口的一种实现后,需要在Jar包的**META-INF/services/**目录下,创建一个以接口的名称(包名.接口名的形式)命名的文件,在文件中配置接口的实现类(完整的包名+类名)。
当外部程序通过 java.util.ServiceLoader 类装载这个接口时,就能够通过该Jar包的**META/Services/**目录里的配置文件找到具体的实现类名,装载实例化,完成注入。同时,SPI的规范规定了接口的实现类必须有一个无参构造方法。
SPI中查找接口的实现类是通过 java.util.ServiceLoader ,而在 java.util.ServiceLoader 类中有一行代码如下:
// 加载具体实现类信息的前缀,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目录下 private static final String PREFIX = "META-INF/services/";
这也就是说,我们必须将接口的配置文件写到Jar包的**META/Services/**目录下。
这里,给出一个简单的SPI使用实例,演示在Java程序中如何使用SPI动态加载接口的实现类。
注意:实例是基于Java8进行开发的。
在IDEA中创建Maven项目spi-demo,如下:

编辑
- "1.0" encoding="UTF-8"?>
"http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-
-
spi-demo -
io.binghe.spi -
jar -
1.0.0-SNAPSHOT -
4.0.0 -
-
-
-
-
org.springframework.boot -
spring-boot-maven-plugin -
-
-
org.apache.maven.plugins -
maven-compiler-plugin -
3.6.0 -
-
1.8 -
1.8 -
-
-
-
在io.binghe.spi.loader包下创建MyServiceLoader,MyServiceLoader类中直接调用JDK的ServiceLoader类加载Class。代码如下所示。
- package io.binghe.spi.loader;
-
- import java.util.ServiceLoader;
-
- /**
- * @author binghe
- * @version 1.0.0
- * @description 类加载工具
- */
- public class MyServiceLoader {
-
-
- /**
- * 使用SPI机制加载所有的Class
- */
- public static
ServiceLoader loadAll(final Class clazz) { -
- return ServiceLoader.load(clazz);
- }
- }
在io.binghe.spi.service包下创建接口MyService,作为测试接口,接口中只有一个方法,打印传入的字符串信息。代码如下所示:
- package io.binghe.spi.service;
-
- /**
- * @author binghe
- * @version 1.0.0
- * @description 定义接口
- */
- public interface MyService {
-
-
- /**
- * 打印信息
- */
- void print(String info);
- }
(1)创建第一个实现类MyServiceA
在io.binghe.spi.service.impl包下创建MyServiceA类,实现MyService接口。代码如下所示:
- package io.binghe.spi.service.impl;
- import io.binghe.spi.service.MyService;
-
- /**
- * @author binghe
- * @version 1.0.0
- * @description 接口的第一个实现
- */
- public class MyServiceA implements MyService {
-
- @Override
- public void print(String info) {
-
- System.out.println(MyServiceA.class.getName() + " print " + info);
- }
- }
(2)创建第二个实现类MyServiceB
在io.binghe.spi.service.impl包下创建MyServiceB类,实现MyService接口。代码如下所示:
- package io.binghe.spi.service.impl;
-
- import io.binghe.spi.service.MyService;
-
- /**
- * @author binghe
- * @version 1.0.0
- * @description 接口第二个实现
- */
- public class MyServiceB implements MyService {
-
- @Override
- public void print(String info) {
-
- System.out.println(MyServiceB.class.getName() + " print " + info);
- }
- }
6.创建接口文件
在项目的src/main/resources目录下创建**META/Services/**目录,在目录中创建io.binghe.spi.service.MyService文件,注意:文件必须是接口MyService的全名,之后将实现MyService接口的类配置到文件中,如下所示:
- io.binghe.spi.service.impl.MyServiceA
- io.binghe.spi.service.impl.MyServiceB
7.创建测试类
在项目的io.binghe.spi.main包下创建Main类,该类为测试程序的入口类,提供一个main()方法,在main()方法中调用ServiceLoader类加载MyService接口的实现类。并通过Java8的Stream将结果打印出来,如下所示:
- package io.binghe.spi.main;
-
- import io.binghe.spi.loader.MyServiceLoader;
- import io.binghe.spi.service.MyService;
-
- import java.util.ServiceLoader;
- import java.util.stream.StreamSupport;
-
- /**
- * @author binghe
- * @version 1.0.0
- * @description 测试的main方法
- */
- public class Main {
-
-
- public static void main(String[] args){
-
- ServiceLoader
loader = MyServiceLoader.loadAll(MyService.class); - StreamSupport.stream(loader.spliterator(), false).forEach(s -> s.print("Hello World"));
- }
- }
8.测试实例
运行Main类中的main()方法,打印出的信息如下所示:
- io.binghe.spi.service.impl.MyServiceA print Hello World
- io.binghe.spi.service.impl.MyServiceB print Hello World
-
- Process finished with exit code 0
通过打印信息可以看出,通过Java SPI机制正确加载出接口的实现类,并调用接口的实现方法。
这里,主要是对SPI的加载流程涉及到的 java.util.ServiceLoader 的源码的解析。
进入 java.util.ServiceLoader 的源码,可以看到ServiceLoader类实现了 java.lang.Iterable 接口,如下所示。
public final class ServiceLoader implements Iterable
说明ServiceLoader类是可以遍历迭代的。
java.util.ServiceLoader类中定义了如下的成员变量:
- // 加载具体实现类信息的前缀,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目录下
- private static final String PREFIX = "META-INF/services/";
-
- // 需要加载的接口
- private final Class
service; -
- // 类加载器,用于加载以接口命名的文件中配置的接口的实现类
- private final ClassLoader loader;
-
- // 创建ServiceLoader时采用的访问控制上下文环境
- private final AccessControlContext acc;
-
- // 用来缓存已经加载的接口实现类,其中,Key是接口实现类的完整类名,Value为实现类对象
- private LinkedHashMap
providers = new LinkedHashMap<>(); -
- // 用于延迟加载实现类的迭代器
- private LazyIterator lookupIterator;
可以看到ServiceLoader类中定义了加载前缀为“META-INF/services/”,所以,接口文件必须要在项目的 src/main/resources 目录下的**META-INF/services/**目录下创建。
从MyServiceLoader类调用**ServiceLoader.load(clazz)**方法进入源码,如下所示:
- //根据类的Class对象加载指定的类,返回ServiceLoader对象
- public static
ServiceLoader load(Class service) { -
- //获取当前线程的类加载器
- ClassLoader cl = Thread.currentThread().getContextClassLoader();
- //动态加载指定的类,将类加载到ServiceLoader中
- return ServiceLoader.load(service, cl);
- }
方法中调用了**ServiceLoader.load(service, cl)**方法,继续跟踪代码,如下所示:
- //通过ClassLoader加载指定类的Class,并将返回结果封装到ServiceLoader对象中
- public static
ServiceLoader load(Class service, ClassLoader loader){ -
- return new ServiceLoader<>(service, loader);
- }
可以看到**ServiceLoader.load(service, cl)**方法中,调用了ServiceLoader类的构造方法,继续跟进代码,如下所示:
- //构造ServiceLoader对象
- private ServiceLoader(Class
svc, ClassLoader cl) { -
- //如果传入的Class对象为空,则判处空指针异常
- service = Objects.requireNonNull(svc, "Service interface cannot be null");
- //如果传入的ClassLoader为空,则通过ClassLoader.getSystemClassLoader()获取,否则直接使用传入的ClassLoader
- loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
- acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
- reload();
- }
继续跟**reload()**方法,如下所示。
- //重新加载
- public void reload() {
-
- //清空保存加载的实现类的LinkedHashMap
- providers.clear();
- //构造延迟加载的迭代器
- lookupIterator = new LazyIterator(service, loader);
- }
继续跟进懒加载迭代器的构造函数,如下所示。
- private LazyIterator(Class
service, ClassLoader loader) { -
- this.service = service;
- this.loader = loader;
- }
可以看到,会将需要加载的接口的Class对象和类加载器赋值给 LazyIterator 的成员变量。
当我们在程序中迭代获取对象实例时,首先在成员变量** providers 中查找是否有缓存的实例对象。如果存在则直接返回,否则调用 lookupIterator **延迟加载迭代器进行加载。
迭代器进行逻辑判断的代码如下所示:
- //迭代ServiceLoader的方法
- public Iterator
iterator() { -
- return new Iterator
() { -
- //获取保存实现类的LinkedHashMap
的迭代器 - Iterator
> knownProviders = providers.entrySet().iterator(); - //判断是否有下一个元素
- public boolean hasNext() {
-
- //如果knownProviders存在元素,则直接返回true
- if (knownProviders.hasNext())
- return true;
- //返回延迟加载器是否存在元素
- return lookupIterator.hasNext();
- }
- //获取下一个元素
- public S next() {
-
- //如果knownProviders存在元素,则直接获取
- if (knownProviders.hasNext())
- return knownProviders.next().getValue();
- //获取延迟迭代器lookupIterator中的元素
- return lookupIterator.next();
- }
-
- public void remove() {
-
- throw new UnsupportedOperationException();
- }
-
- };
- }
- LazyIterator加载类的流程如下代码所示
-
- //判断是否拥有下一个实例
- private boolean hasNextService() {
-
- //如果拥有下一个实例,直接返回true
- if (nextName != null) {
-
- return true;
- }
- //如果实现类的全名为null
- if (configs == null) {
-
- try {
-
- //获取全文件名,文件相对路径+文件名称(包名+接口名)
- String fullName = PREFIX + service.getName();
- //类加载器为空,则通过ClassLoader.getSystemResources()方法获取
- 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()) {
-
- //如果configs中没有更过的元素,则直接返回false
- if (!configs.hasMoreElements()) {
-
- return false;
- }
- //解析包结构
- pending = parse(service, configs.nextElement());
- }
- nextName = pending.next();
- return true;
- }
-
- 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,
- "Provider " + cn + " not found");
- }
- if (!service.isAssignableFrom(c)) {
-
- fail(service,
- "Provider " + cn + " not a subtype");
- }
- try {
-
- //通过c.newInstance()生成对象实例
- S p = service.cast(c.newInstance());
- //将生成的对象实例保存到缓存中(LinkedHashMap
) - providers.put(cn, p);
- return p;
- } catch (Throwable x) {
-
- fail(service,
- "Provider " + cn + " could not be instantiated",
- x);
- }
- throw new Error(); // This cannot happen
- }
-
- public boolean hasNext() {
-
- if (acc == null) {
-
- return hasNextService();
- } else {
-
- PrivilegedAction
action = new PrivilegedAction() { -
- public Boolean run() {
- return hasNextService(); }
- };
- return AccessController.doPrivileged(action, acc);
- }
- }
-
- public S next() {
-
- if (acc == null) {
-
- return nextService();
- } else {
-
- PrivilegedAction
action = new PrivilegedAction() { -
- public S run() {
- return nextService(); }
- };
- return AccessController.doPrivileged(action, acc);
- }
- }
最后,给出整个 java.util.ServiceLoader 的类,如下所示:
- package java.util;
-
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.net.URL;
- import java.security.AccessControlContext;
- import java.security.AccessController;
- import java.security.PrivilegedAction;
-
-
- public final class ServiceLoader
implements Iterable { -
- // 加载具体实现类信息的前缀,也就是以接口命名的文件需要放到Jar包中的META-INF/services/目录下
- private static final String PREFIX = "META-INF/services/";
-
- // 需要加载的接口
- private final Class
service; -
- // 类加载器,用于加载以接口命名的文件中配置的接口的实现类
- private final ClassLoader loader;
-
- // 创建ServiceLoader时采用的访问控制上下文环境
- private final AccessControlContext acc;
-
- // 用来缓存已经加载的接口实现类,其中,Key是接口实现类的完整类名,Value为实现类对象
- private LinkedHashMap
providers = new LinkedHashMap<>(); -
- // 用于延迟加载实现类的迭代器
- private LazyIterator lookupIterator;
-
- //重新加载
- public void reload() {
-
- //清空保存加载的实现类的LinkedHashMap
- providers.clear();
- //构造延迟加载的迭代器
- lookupIterator = new LazyIterator(service, loader);
- }
-
- //构造ServiceLoader对象
- private ServiceLoader(Class
svc, ClassLoader cl) { -
- //如果传入的Class对象为空,则判处空指针异常
- service = Objects.requireNonNull(svc, "Service interface cannot be null");
- //如果传入的ClassLoader为空,则通过ClassLoader.getSystemClassLoader()获取,否则直接使用传入的ClassLoader
- loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
- acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
- reload();
- }
-
- private static void fail(Class> service, String msg, Throwable cause)
- throws ServiceConfigurationError
- {
-
- throw new ServiceConfigurationError(service.getName() + ": " + msg,
- cause);
- }
-
- private static void fail(Class> service, String msg)
- throws ServiceConfigurationError
- {
-
- throw new ServiceConfigurationError(service.getName() + ": " + msg);
- }
-
- private static void fail(Class> service, URL u, int line, String msg)
- throws ServiceConfigurationError
- {
-
- fail(service, u + ":" + line + ": " + msg);
- }
-
- // Parse a single line from the given configuration file, adding the name
- // on the line to the names list.
- //
- private int parseLine(Class> service, URL u, BufferedReader r, int lc,
- List
names) - throws IOException, ServiceConfigurationError
- {
-
- String ln = r.readLine();
- if (ln == null) {
-
- return -1;
- }
- int ci = ln.indexOf('#');
- if (ci >= 0) ln = ln.substring(0, ci);
- ln = ln.trim();
- int n = ln.length();
- if (n != 0) {
-
- if ((ln.indexOf(' ') >= 0) || (ln.indexOf('\t') >= 0))
- fail(service, u, lc, "Illegal configuration-file syntax");
- int cp = ln.codePointAt(0);
- if (!Character.isJavaIdentifierStart(cp))
- fail(service, u, lc, "Illegal provider-class name: " + ln);
- for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
-
- cp = ln.codePointAt(i);
- if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
- fail(service, u, lc, "Illegal provider-class name: " + ln);
- }
- if (!providers.containsKey(ln) && !names.contains(ln))
- names.add(ln);
- }
- return lc + 1;
- }
-
- private Iterator
parse(Class> service, URL u) - throws ServiceConfigurationError
- {
-
- InputStream in = null;
- BufferedReader r = null;
- ArrayList
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();
- }
-
- // Private inner class implementing fully-lazy provider lookupload
- private class LazyIterator
- implements Iterator
- {
-
-
- Class
service; - ClassLoader loader;
- Enumeration
configs = null; - Iterator
pending = null; - String nextName = null;
-
- private LazyIterator(Class
service, ClassLoader loader) { -
- this.service = service;
- this.loader = loader;
- }
-
- //判断是否拥有下一个实例
- private boolean hasNextService() {
-
- //如果拥有下一个实例,直接返回true
- if (nextName != null) {
-
- return true;
- }
- //如果实现类的全名为null
- if (configs == null) {
-
- try {
-
- //获取全文件名,文件相对路径+文件名称(包名+接口名)
- String fullName = PREFIX + service.getName();
- //类加载器为空,则通过ClassLoader.getSystemResources()方法获取
- 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()) {
-
- //如果configs中没有更过的元素,则直接返回false
- if (!configs.hasMoreElements()) {
-
- return false;
- }
- //解析包结构
- pending = parse(service, configs.nextElement());
- }
- nextName = pending.next();
- return true;
- }
-
- 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,
- "Provider " + cn + " not found");
- }
- if (!service.isAssignableFrom(c)) {
-
- fail(service,
- "Provider " + cn + " not a subtype");
- }
- try {
-
- //通过c.newInstance()生成对象实例
- S p = service.cast(c.newInstance());
- //将生成的对象实例保存到缓存中(LinkedHashMap
) - providers.put(cn, p);
- return p;
- } catch (Throwable x) {
-
- fail(service,
- "Provider " + cn + " could not be instantiated",
- x);
- }
- throw new Error(); // This cannot happen
- }
-
- public boolean hasNext() {
-
- if (acc == null) {
-
- return hasNextService();
- } else {
-
- PrivilegedAction
action = new PrivilegedAction() { -
- public Boolean run() {
- return hasNextService(); }
- };
- return AccessController.doPrivileged(action, acc);
- }
- }
-
- public S next() {
-
- if (acc == null) {
-
- return nextService();
- } else {
-
- PrivilegedAction
action = new PrivilegedAction() { -
- public S run() {
- return nextService(); }
- };
- return AccessController.doPrivileged(action, acc);
- }
- }
-
- public void remove() {
-
- throw new UnsupportedOperationException();
- }
-
- }
-
- //迭代ServiceLoader的方法
- public Iterator
iterator() { -
- return new Iterator
() { -
- //获取保存实现类的LinkedHashMap
的迭代器 - Iterator
> knownProviders = providers.entrySet().iterator(); - //判断是否有下一个元素
- public boolean hasNext() {
-
- //如果knownProviders存在元素,则直接返回true
- if (knownProviders.hasNext())
- return true;
- //返回延迟加载器是否存在元素
- return lookupIterator.hasNext();
- }
- //获取下一个元素
- public S next() {
-
- //如果knownProviders存在元素,则直接获取
- if (knownProviders.hasNext())
- return knownProviders.next().getValue();
- //获取延迟迭代器lookupIterator中的元素
- return lookupIterator.next();
- }
-
- public void remove() {
-
- throw new UnsupportedOperationException();
- }
-
- };
- }
-
- //通过ClassLoader加载指定类的Class,并将返回结果封装到ServiceLoader对象中
- public static
ServiceLoader load(Class service, - ClassLoader loader)
- {
-
- return new ServiceLoader<>(service, loader);
- }
-
- //根据类的Class对象加载指定的类,返回ServiceLoader对象
- public static
ServiceLoader load(Class service) { -
- //获取当前线程的类加载器
- ClassLoader cl = Thread.currentThread().getContextClassLoader();
- //动态加载指定的类,将类加载到ServiceLoader中
- return ServiceLoader.load(service, cl);
- }
-
- public static
ServiceLoader loadInstalled(Class service) { -
- ClassLoader cl = ClassLoader.getSystemClassLoader();
- ClassLoader prev = null;
- while (cl != null) {
-
- prev = cl;
- cl = cl.getParent();
- }
- return ServiceLoader.load(service, prev);
- }
-
- /**
- * Returns a string describing this service.
- *
- * @return A descriptive string
- */
- public String toString() {
-
- return "java.util.ServiceLoader[" + service.getName() + "]";
- }
-
- }
最后,对Java提供的SPI机制进行简单的总结。
能够实现项目解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。