共计 15702 个字符,预计需要花费 40 分钟才能阅读完成。
写在后面
SPI 机制可能十分不便的为某个接口动静指定其实现类,在某种程度上,这也是某些框架具备高度可扩展性的根底。明天,咱们就从源码级别深入探讨下 Java 中的 SPI 机制。
注:文章已收录到:https://github.com/sunshinelyz/technology-binghe
SPI 的概念
SPI 在 Java 中的全称为 Service Provider Interface,是 JDK 内置的一种服务提供发现机制,是 Java 提供的一套用来被第三方实现或者扩大的 API,它能够用来启用框架扩大和替换组件。
JAVA SPI = 基于接口的编程+策略模式+配置文件的动静加载机制
SPI 的应用场景
Java 是一种面向对象语言,尽管 Java8 开始反对函数式编程和 Stream,然而总体来说,还是面向对象的语言。在应用 Java 进行面向对象开发时,个别会举荐应用基于接口的编程,程序的模块与模块之前不会间接进行实现类的硬编码。而在理论的开发过程中,往往一个接口会有多个实现类,各实现类要么实现的逻辑不同,要么应用的形式不同,还有的就是实现的技术不同。为了使调用方在调用接口的时候,明确的晓得本人调用的是接口的哪个实现类,或者说为了实现在模块拆卸的时候不必在程序里动静指明,这就须要一种服务发现机制。Java 中的 SPI 加载机制可能满足这样的需要,它可能主动寻找某个接口的实现类。
大量的框架应用了 Java 的 SPI 技术,如下:
(1)JDBC 加载不同类型的数据库驱动
(2)日志门面接口实现类加载,SLF4J 加载不同提供商的日志实现类
(3)Spring 中大量应用了 SPI
- 对 servlet3.0 标准
- 对 ServletContainerInitializer 的实现
- 主动类型转换 Type Conversion SPI(Converter SPI、Formatter SPI) 等
(4)Dubbo 外面有很多个组件,每个组件在框架中都是以接口的造成形象进去!具体的实现又分很多种,在程序执行时依据用户的配置来按需取接口的实现
SPI 的应用
当服务的提供者,提供了接口的一种实现后,须要在 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 实例
这里,给出一个简略的 SPI 应用实例,演示在 Java 程序中如何应用 SPI 动静加载接口的实现类。
留神:实例是基于 Java8 进行开发的。
1. 创立 Maven 我的项目
在 IDEA 中创立 Maven 我的项目 spi-demo,如下:
2. 编辑 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="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">
<artifactId>spi-demo</artifactId>
<groupId>io.binghe.spi</groupId>
<packaging>jar</packaging>
<version>1.0.0-SNAPSHOT</version>
<modelVersion>4.0.0</modelVersion>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
3. 创立类加载工具类
在 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 <S> ServiceLoader<S> loadAll(final Class<S> clazz) {return ServiceLoader.load(clazz);
}
}
4. 创立接口
在 io.binghe.spi.service 包下创立接口 MyService,作为测试接口,接口中只有一个办法,打印传入的字符串信息。代码如下所示:
package io.binghe.spi.service;
/**
* @author binghe
* @version 1.0.0
* @description 定义接口
*/
public interface MyService {
/**
* 打印信息
*/
void print(String info);
}
5. 创立接口的实现类
(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<MyService> 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<S> implements Iterable<S>
阐明 ServiceLoader 类是能够遍历迭代的。
java.util.ServiceLoader 类中定义了如下的成员变量:
// 加载具体实现类信息的前缀,也就是以接口命名的文件须要放到 Jar 包中的 META-INF/services/ 目录下
private static final String PREFIX = "META-INF/services/";
// 须要加载的接口
private final Class<S> service;
// 类加载器,用于加载以接口命名的文件中配置的接口的实现类
private final ClassLoader loader;
// 创立 ServiceLoader 时采纳的访问控制上下文环境
private final AccessControlContext acc;
// 用来缓存曾经加载的接口实现类,其中,Key 是接口实现类的残缺类名,Value 为实现类对象
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 用于提早加载实现类的迭代器
private LazyIterator lookupIterator;
能够看到 ServiceLoader 类中定义了加载前缀为“META-INF/services/”,所以,接口文件必须要在我的项目的 src/main/resources 目录下的 META-INF/services/ 目录下创立。
从 MyServiceLoader 类调用 ServiceLoader.load(clazz) 办法进入源码,如下所示:
// 依据类的 Class 对象加载指定的类,返回 ServiceLoader 对象
public static <S> ServiceLoader<S> load(Class<S> service) {
// 获取以后线程的类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
// 动静加载指定的类,将类加载到 ServiceLoader 中
return ServiceLoader.load(service, cl);
}
办法中调用了 ServiceLoader.load(service, cl) 办法,持续跟踪代码,如下所示:
// 通过 ClassLoader 加载指定类的 Class,并将返回后果封装到 ServiceLoader 对象中
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){return new ServiceLoader<>(service, loader);
}
能够看到 ServiceLoader.load(service, cl) 办法中,调用了 ServiceLoader 类的构造方法,持续跟进代码,如下所示:
// 结构 ServiceLoader 对象
private ServiceLoader(Class<S> 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<S> service, ClassLoader loader) {
this.service = service;
this.loader = loader;
}
能够看到,会将须要加载的接口的 Class 对象和类加载器赋值给 LazyIterator 的成员变量。
当咱们在程序中迭代获取对象实例时,首先在成员变量 providers 中查找是否有缓存的实例对象。如果存在则间接返回,否则调用 lookupIterator 提早加载迭代器进行加载。
迭代器进行逻辑判断的代码如下所示:
// 迭代 ServiceLoader 的办法
public Iterator<S> iterator() {return new Iterator<S>() {
// 获取保留实现类的 LinkedHashMap<String,S> 的迭代器
Iterator<Map.Entry<String,S>> 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<String,S>)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<Boolean> action = new PrivilegedAction<Boolean>() {public Boolean run() {return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public S next() {if (acc == null) {return nextService();
} else {PrivilegedAction<S> action = new PrivilegedAction<S>() {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<S> implements Iterable<S> {
// 加载具体实现类信息的前缀,也就是以接口命名的文件须要放到 Jar 包中的 META-INF/services/ 目录下
private static final String PREFIX = "META-INF/services/";
// 须要加载的接口
private final Class<S> service;
// 类加载器,用于加载以接口命名的文件中配置的接口的实现类
private final ClassLoader loader;
// 创立 ServiceLoader 时采纳的访问控制上下文环境
private final AccessControlContext acc;
// 用来缓存曾经加载的接口实现类,其中,Key 是接口实现类的残缺类名,Value 为实现类对象
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 用于提早加载实现类的迭代器
private LazyIterator lookupIterator;
// 从新加载
public void reload() {
// 清空保留加载的实现类的 LinkedHashMap
providers.clear();
// 结构提早加载的迭代器
lookupIterator = new LazyIterator(service, loader);
}
// 结构 ServiceLoader 对象
private ServiceLoader(Class<S> 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<String> 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<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();}
// Private inner class implementing fully-lazy provider lookupload
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;
}
// 判断是否领有下一个实例
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<String,S>)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<Boolean> action = new PrivilegedAction<Boolean>() {public Boolean run() {return hasNextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public S next() {if (acc == null) {return nextService();
} else {PrivilegedAction<S> action = new PrivilegedAction<S>() {public S run() {return nextService(); }
};
return AccessController.doPrivileged(action, acc);
}
}
public void remove() {throw new UnsupportedOperationException();
}
}
// 迭代 ServiceLoader 的办法
public Iterator<S> iterator() {return new Iterator<S>() {
// 获取保留实现类的 LinkedHashMap<String,S> 的迭代器
Iterator<Map.Entry<String,S>> 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 <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{return new ServiceLoader<>(service, loader);
}
// 依据类的 Class 对象加载指定的类,返回 ServiceLoader 对象
public static <S> ServiceLoader<S> load(Class<S> service) {
// 获取以后线程的类加载器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
// 动静加载指定的类,将类加载到 ServiceLoader 中
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> loadInstalled(Class<S> 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() + "]";
}
}
SPI 总结
最初,对 Java 提供的 SPI 机制进行简略的总结。
长处:
可能实现我的项目解耦,使得第三方服务模块的拆卸管制的逻辑与调用者的业务代码拆散,而不是耦合在一起。应用程序能够依据理论业务状况启用框架扩大或替换框架组件。
毛病:
- 多个并发多线程应用 ServiceLoader 类的实例是不平安的
- 尽管 ServiceLoader 也算是应用的提早加载,然而根本只能通过遍历全副获取,也就是接口的实现类全副加载并实例化一遍。
参考:深刻了解 Java 中的 spi 机制
重磅福利
微信搜一搜【冰河技术】微信公众号,关注这个有深度的程序员,每天浏览超硬核技术干货,公众号内回复【PDF】有我筹备的一线大厂面试材料和我原创的超硬核 PDF 技术文档,以及我为大家精心筹备的多套简历模板(不断更新中),心愿大家都能找到心仪的工作,学习是一条时而郁郁寡欢,时而开怀大笑的路,加油。如果你通过致力胜利进入到了心仪的公司,肯定不要懈怠放松,职场成长和新技术学习一样,逆水行舟。如果有幸咱们江湖再见!
另外,我开源的各个 PDF,后续我都会继续更新和保护,感激大家长期以来对冰河的反对!!