1.简述
SPI(Service Provider Interface)是一种服务发现机制,能够动态替换与发现服务,将接口与实现类解耦
SPI机制的实现方式是,在配置文件中定义好某个接口的实现类,然后根据接口名找到配置文件中的实现类名,从而加载这个实现类
SPI机制的优点:
1)解耦,将接口和具体实现分离开,使得应用程序可以根据自身业务需求启用不同的第三方实现服务
2)提高框架的扩展性,避免将实现类硬编码在代码中
2.基本使用
1)SPI机制基于接口实现,因此首先创建一个接口
package com.limin.study.spi;
public interface IService {
void doSomething();
}
2)在resources目录下创建META-INF/services
文件夹,其中放入以全限定接口名命名的文件,例如文件名称为com.limin.study.spi.IService
3)创建接口实现类
package com.limin.study.other;
import com.limin.study.spi.IService;
public class MyServiceImpl1 implements IService {
@Override
public void doSomething() {
System.out.println("MyServiceImpl1 do something...");
}
}
package com.limin.study.other;
import com.limin.study.spi.IService;
public class MyServiceImpl2 implements IService {
@Override
public void doSomething() {
System.out.println("MyServiceImpl2 do something...");
}
}
4)文件内容为该接口的实现类的全限定类名,例如:
com.limin.study.other.MyServiceImpl1
com.limin.study.other.MyServiceImpl2
5)测试通过SPI机制调用实现类doSomething()
,代码如下:
package com.limin.study.spi;
import java.util.Iterator;
import java.util.ServiceLoader;
public class SpiTest {
public static void main(String[] args) {
ServiceLoader<IService> loader = ServiceLoader.load(IService.class);
Iterator<IService> iterator = loader.iterator();
while (iterator.hasNext()) {
IService serviceImpl = iterator.next();
serviceImpl.doSomething();
}
}
}
6)测试结果如下:
MyServiceImpl1 do something...
MyServiceImpl2 do something...
3.源码分析
SPI的核心类是ServiceLoader,核心原理分析如下:
1)通过ServiceLoader.load()
创建ServiceLoader对象过程见源码:
public final class ServiceLoader<S> implements Iterable<S> {
// 文件名称前缀,因此需要将文件放在resources/META-INF/services目录下
private static final String PREFIX = "META-INF/services/";
private final Class<S> service;
private final ClassLoader loader;
private final AccessControlContext acc;
private LinkedHashMap<String, S> providers = new LinkedHashMap<>();
private LazyIterator lookupIterator;
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader) {
return new ServiceLoader<>(service, loader);
}
public static <S> ServiceLoader<S> load(Class<S> service) {
// 获取线程上下文类加载器,一般为AppClassLoader
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {
// 校验传入的接口类不能为空
service = Objects.requireNonNull(svc, "Service interface cannot be null");
// 如果传入的类加载器为空,则设置为系统类加载器,一般也为AppClassLoader
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
public void reload() {
providers.clear();
// 创建迭代器对象
lookupIterator = new LazyIterator(service, loader);
}
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;
}
// 迭代器中其他方法
}
// 其他方法...
}
由于ServiceLoader位于rt.jar中,因此该类是被BootStrapClassLoader加载的,而ServiceLoader需要加载我们自定义的类,而BootStrapClassLoader是加载不了类路径下定义的类的,所以默认情况下,源码中通过Thread.currentThread().getContextClassLoader()
获取了线程上线文类加载器,一般为AppClassLoader,使用线程上线文类加载器就能够加载我们的实现类了
2)ServiceLoader实现了Iterator接口,通过loader.iterator()
可以获取迭代器对象
public Iterator<S> iterator() {
// 返回了一个Iterator的实现类
return new Iterator<S>() {
// 缓存
Iterator<Map.Entry<String,S>> knownProviders = providers.entrySet().iterator();
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
// 如果缓存中没有,调用的是LazyIterator中的方法
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
// 如果缓存中没有,调用的是LazyIterator中的方法
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
3)通过迭代器hasNext()
和next()
获取实现类
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() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
// 1.获取文件名称,文件名为:resources/META-INF/services/全限定接口名
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
// 2.根据文件名称获取当前路径及jar包中的配置的SPI文件路径
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
// 解析SPI配置文件,解析的过程就是按行读取文件内容
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 {
// 1.加载实现类
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 {
// 2.创建实现类对象
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();
}
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();
}
}
从源码可以看出,创建实现类的过程如下:
1.读取配置文件解析出实现类名称
2.通过反射创建实现类对象
4.框架中的使用
SPI机制在很多框架中都有使用,例如JDBC加载驱动、SpringBoot自动配置等
4.1 加载MySQL驱动类
1)我们知道使用JDBC操作数据库时需要先通过DriverManager.getConnection获取连接,例如:
private Connection getConnection() {
Connection connection;
try {
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8&serverTimezone=UTC", "root", "123456");
} catch (SQLException e) {
LOGGER.error("get connection failed", e);
return null;
}
return connection;
}
2)DriverManager
中静态代码块会通过SPI加载MySQL驱动,java.sql.DriverManager
源码如下:
public class DriverManager {
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
String drivers;
try {
drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
public String run() {
return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {
drivers = null;
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// 1.通过SPI机制在类路径及jar包中META-INF/services查找java.sql.Driver文件
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
// 2.加载类
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
println("DriverManager.initialize: jdbc.drivers = " + drivers);
if (drivers == null || drivers.equals("")) {
return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {
try {
println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {
println("DriverManager.Initialize: load failed: " + ex);
}
}
}
// 其他代码省略...
}
3)ServiceLoader.load(Driver.class)
查找META-INF/services/java.sql.Driver
文件
MySQL驱动包中SPI配置文件如下图所示:
driversIterator.next()
会调用迭代器中的nextService()
,从而根据实现类的全限定类名调用Class.forName(cn, false, loader)
加载com.mysql.cj.jdbc.Driver
驱动类
4)驱动类com.mysql.cj.jdbc.Driver
源码如下:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
// 注册驱动到DriverManager的成员变量registeredDrivers中
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
可以看到静态代码块中会调用DriverManager.registerDriver
注册驱动
5)注册成功后,调用getConnection()
时就能通过对应的驱动建立连接,DriverManager#getConnection
源码如下:
private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {
// 省略其他代码...
for (DriverInfo aDriver : registeredDrivers) {
if (isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
// 通过注册的驱动对象建立连接
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
// 省略其他代码...
}
4.2 加载表达式处理引擎
在项目开发中,我们可能会使用到Hutool工具包中的ExpressionEngine执行表达式计算,比如说需要执行SpEL表达式,可以调用ExpressionUtil#eval,源码如下所示:
public static Object eval(String expression, Map<String, Object> context) {
return eval(expression, context, ListUtil.empty());
}
public static Object eval(String expression, Map<String, Object> context, Collection<Class<?>> allowClassSet) {
return getEngine().eval(expression, context, allowClassSet);
}
public static ExpressionEngine getEngine() {
return ExpressionFactory.get();
}
其中获取具体引擎实现类是通过SPI机制实现的,ExpressionFactory.get()
会调用ExpressionFactory#doCreate
private static ExpressionEngine doCreate() {
final ExpressionEngine engine = ServiceLoaderUtil.loadFirstAvailable(ExpressionEngine.class);
if(null != engine){
return engine;
}
throw new ExpressionException("No expression jar found ! Please add one of it to your project !");
}
进入到ServiceLoaderUtil.loadFirstAvailable方法,返回第一个能够加载成功的实现类
public static <T> T loadFirstAvailable(Class<T> clazz) {
final Iterator<T> iterator = load(clazz).iterator();
while (iterator.hasNext()) {
try {
// 内部会调用class.forName加载类
return iterator.next();
} catch (ServiceConfigurationError ignore) {
// ignore
}
}
return null;
}
public static <T> ServiceLoader<T> load(Class<T> clazz) {
return load(clazz, null);
}
public static <T> ServiceLoader<T> load(Class<T> clazz, ClassLoader loader) {
return ServiceLoader.load(clazz, ObjectUtil.defaultIfNull(loader, ClassLoaderUtil::getClassLoader));
}
配置文件如下所示:
在每个具体实现类的构造函数中都会创建对应的引擎解析类,这些解析类依赖其他的jar包,如果加载了这些jar包就能顺利创建出对应的实现类,例如SpELEngine实例化时会创建SpelExpressionParser,这个类来自于spring-expression
public class SpELEngine implements ExpressionEngine {
private final ExpressionParser parser;
public SpELEngine(){
parser = new SpelExpressionParser();
}
@Override
public Object eval(String expression, Map<String, Object> context, Collection<Class<?>> allowClassSet) {
final EvaluationContext evaluationContext = new StandardEvaluationContext();
context.forEach(evaluationContext::setVariable);
return parser.parseExpression(expression).getValue(evaluationContext);
}
}
即如果类加载了spring-expression的jar包,那么就能创建SpELEngine引擎处理类