一、简介
A service is a well-known set of interfaces and (usually abstract) classes. A service provider is a specific implementation of a service. The classes in a provider typically implement the interfaces and subclass the classes defined in the service itself. Service providers can be installed in an implementation of the Java platform in the form of extensions, that is, jar files placed into any of the usual extension directories. Providers can also be made available by adding them to the application’s class path or by some other platform-specific means.
SPI(Service Provider Interface),是JDK内置的一种服务提供发现机制,旨在由第三方实现或扩展的API 。它可用于启用框架扩展和可替换组件。
二、使用
spi 的使用场景,一般是提供一个可由第三方扩展的服务接口。并提供加载机制。(1-2,一般是一个基础jar包)
用户可以扩展自定义服务的具体实现,注册后,底层的加载机制就可以加载出来使用了。(3-4,一般是写在扩展 jar 包)
比如以模拟 java.jdbc.Driver 为例:
1、定义服务接口
package com.c.service;
public interface Driver {
String getName();
void connect();
void disConnect();
}
2、实现服务加载以及使用类
JDK 中查找服务的实现的工具类是:java.util.ServiceLoader 。
package com.c.service;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;
/**
* @author c
*/
public class Connector {
private static final List<Driver> drivers = new ArrayList<>();
@SuppressWarnings("squid:S3077")
private static volatile Connector instance;
private Connector() {
init();
}
private void init() {
// SPIUtils.foreachRemaining(Driver.class, drivers::add);
// 查找 服务实现类
ServiceLoader<Driver> loader = ServiceLoader.load(Driver.class);
Iterator<Driver> iterator = loader.iterator();
while (iterator.hasNext()) {
try {
Driver next = iterator.next();
if (next == null) {
continue;
}
drivers.add(next);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static Connector getInstance() {
if (instance == null) {
synchronized (Connector.class) {
if (instance == null) {
instance = new Connector();
}
}
}
return instance;
}
public Driver[] getDrivers() {
return drivers.toArray(new Driver[0]);
}
}
3、实现服务接口
package com.extension.service.impl;
import com.c.service.Driver;
/**
* @author c
*/
public class MySQLDriver implements Driver {
@Override
public String getName() {
return "mysql";
}
@Override
public void connect() {
System.out.println("连接到 mysql");
}
@Override
public void disConnect() {
System.out.println("关闭 mysql 连接");
}
}
package com.extension.service.impl;
import com.c.service.Driver;
/**
* @author c
*/
public class OracleDriver implements Driver {
@Override
public String getName() {
return "oracle";
}
@Override
public void connect() {
System.out.println("连接到 oracle");
}
@Override
public void disConnect() {
System.out.println("关闭 oracle 连接");
}
}
4、注册实现类
当服务的提供者提供了一种接口的实现之后,需要在 cp 目录下的 META-INF/services/ 目录创建一个以服务接口全限定类名命名的文件。这个文件里的内容就是这个服务接口的实现类。当其他程序需要这个服务的时候,就可以通过查找 META-INF/services/ 里配置文件中的实现类名,实例化服务对象。就可以使用该服务了。
com.extension.service.impl.MySQLDriver
com.extension.service.impl.OracleDriver
5、使用
public static void main(String[] args) {
Driver[] drivers = Connector.getInstance().getDrivers();
for (Driver driver : drivers) {
System.out.println("===== 加载到 Driver " + driver.getName() + "=====");
driver.connect();
driver.disConnect();
}
}
输出:
===== 加载到 Driver...mysql=====
连接到 mysql
关闭 mysql 连接
===== 加载到 Driver...oracle=====
连接到 oracle
关闭 oracle 连接
三、封装一个 SPI 加载的工具类
import java.util.Iterator;
import java.util.ServiceLoader;
import java.util.function.Consumer;
public class SPIUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(SPIUtils.class);
private SPIUtils() {}
public static <T> void foreachRemaining(Class<T> clazz, Consumer<? super T> action) {
if (action == null) {
return;
}
ServiceLoader<T> loader = ServiceLoader.load(clazz);
Iterator<T> iterator = loader.iterator();
while (iterator.hasNext()) {
try {
T next = iterator.next();
if (next == null) {
continue;
}
action.accept(next);
} catch (Exception e) {
LOGGER.warn(e.getMessage(), e);
}
}
}
}
参考: