The built-in service discovery mechanism in Java
Running with Java 11.0.10
SPI (Service Provider Interface) 是自 Java 1.6 引入的一种基于接口或抽象类的服务发现机制。得益于 Java SPI 机制,开发人员只需为第三方预留出 SPI 拓展接口,这样可以在不修改代码的前提下,通过增删第三方依赖来实现系统的灵活拓展。
要想成功地玩转 Java SPI,下面四个组件是缺一不可的:
① Service Provider Interface
服务供应商接口,即SPI拓展接口;只能是接口
或抽象类
。
② Service Provider
服务供应商,即针对SPI拓展接口提供SPI实现类的第三方;SPI实现类必须定义一个无参构造方法
,否则报错:Unable to get public no-arg constructor
。
③ SPI Configuration File
首先,SPI配置文件必须贮存于classpath:/META-INF/services/
目录下;其次,其必须采用UTF-8
编码;此外,其必须以SPI拓展接口的完全限定名来命名;最后,SPI配置文件的内容应当为第三方SPI实现类的完全限定名。
④ ServiceLoader
ServiceLoader
是JDK内置的SPI利器,主要负责读取SPI配置文件并将第三方SPI实现类加载到JVM中。
1. SPI应用案例解读
既然是亲儿子,SPI机制在JDK内部还是有若干应用场景的,其中大家最为熟悉的应该就是JDBC API
了。众所周知,官方只是制定了一套数据库交互规范,秉持'让专业的人干专业的事'这一原则,官方并没有提供具体的实现,转而将实现逻辑交由各数据库厂商负责。在JDBC 4.0前后,分别颖现出两种编程范式,如下所示:
Before JDBC 4.0
代码语言:javascript复制Connection connection = null;
Statement statement = null;
try {
// 加载驱动
Class.forName("org.postgresql.Driver");
connection = DriverManager.getConnection("jdbc:postgresql://HOST:PORT/DB", "USERNAME", "PASSWORD");
statement = connection.createStatement();
statement.execute("delete from tbl_user where user_id = 1");
} catch (SQLException e) {
log.error("user deletion failure", e);
} finally {
JdbcUtils.closeStatement(statement);
JdbcUtils.closeConnection(connection);
}
After JDBC 4.0
代码语言:javascript复制Connection connection = null;
Statement statement = null;
try {
connection = DriverManager.getConnection("jdbc:postgresql://HOST:PORT/DB", "USERNAME", "PASSWORD");
statement = connection.createStatement();
statement.execute("delete from tbl_user where user_id = 1");
} catch (SQLException e) {
log.error("user deletion failure", e);
} finally {
JdbcUtils.closeStatement(statement);
JdbcUtils.closeConnection(connection);
}
显然,在JDBC 4.0之前,我们需要通过
Class.forName()
来手动加载指定厂商的数据库驱动;若后期更换数据库驱动,必须修改forName()
方法中的驱动参数。而在JDBC 4.0之后,因为不再需要手动加载数据库驱动,顾而也就不涉及代码的修改了,这就是Java SPI带给我们的能力!
java.sql.Driver
是JDK为第三方数据库厂商预留的SPI拓展接口,主要用于构建Connection。
public interface Driver {
Connection connect(String url, Properties info);
boolean acceptsURL(String url);
}
这里以PostgreSQL为例!org.postgresql:postgresql
驱动包结构如下:
从上图来看,META-INF/services
目录下的java.sql.Driver
文件应该就是SPI配置文件了,其内容如下:
org.postgresql.Driver
显然,PostgreSQL作为数据库厂商,org.postgresql.Driver
毫无保留地实现了java.sql.Driver
接口,在其源码中有一静态初始化代码块,用于向java.sql.DriverManager
注册自身实例。
public class Driver implements java.sql.Driver {
private static Driver registeredDriver;
// 静态初始化代码块
static {
try {
if (Objects.nonNull(registeredDriver)) {
throw new IllegalStateException("Driver is already registered.");
}
registeredDriver = new Driver();
// 第三方厂商向DriverManager注册驱动
DriverManager.registerDriver(registeredDriver);
} catch (SQLException e) {
throw new ExceptionInInitializerError(e);
}
}
@Override
public Connection connect(String url, Properties info) {
// 略
}
@Override
public boolean acceptsURL(String url) {
// 略
}
}
接下来,自然要看看java.sql.DriverManager
的源码了,其主要用来维护驱动实例;registerDriver()
静态方法会将驱动实例添加到CopyOnWriteArrayList中,而deregisterDriver()
静态方法又会将驱动实例从CopyOnWriteArrayList中移除;在getDriver()
和getConnection()
这俩静态方法中,存在一段相同的逻辑,即通过ServiceLoader.load(Driver.class)
来加载PostgreSQL针对java.sql.Driver
接口提供的SPI实现类。关于类的加载,一般通过Class.forName()
方法来实现,其会触发静态初始化代码块的执行,那也就是说org.postgresql.Driver
中的静态初始化代码块是在这里被触发执行的。但为什么ServiceLoader.load(Driver.class)
执行完之后,还要有一个空的迭代逻辑呢?
public class DriverManager {
// 已注册JDBC驱动列表
private static final CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
// 锁
private static final Object lockForInitDrivers = new Object();
// 第三方厂商驱动是否已经加载完毕,若已加载,则后期不再重复加载
private static volatile boolean driversInitialized;
// 私有构造方法
private DriverManager(){}
// 获取Driver实例
public static Driver getDriver(String url) {
if (!driversInitialized) {
synchronized (lockForInitDrivers) {
if (!driversInitialized) {
// ServiceLoader.load()并不会立即去加载第三方厂商驱动,其只是返回一个ServiceLoader实例而已
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
// 生成Iterator实例
Iterator<Driver> driversIterator = loadedDrivers.iterator();
// 延迟加载,即在迭代时才真正去加载第三方厂商驱动
while (driversIterator.hasNext()) {
driversIterator.next();
}
driversInitialized = true;
}
}
}
Driver driver = null;
for (DriverInfo driverInfo : registeredDrivers) {
if (driverInfo.driver.acceptsURL(url)) {
driver = driverInfo.driver;
break;
}
}
return driver;
}
// 获取Connection实例
public static Connection getConnection(String url, String user, String password) {
if (!driversInitialized) {
synchronized (lockForInitDrivers) {
if (!driversInitialized) {
// ServiceLoader.load()并不会立即去加载第三方厂商驱动,其只是返回一个ServiceLoader实例而已
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
// 生成Iterator实例
Iterator<Driver> driversIterator = loadedDrivers.iterator();
// 延迟加载,即在迭代时才真正去加载第三方厂商驱动
while (driversIterator.hasNext()) {
driversIterator.next();
}
driversInitialized = true;
}
}
}
Properties info = MapUtils.toProperties(ImmutableMap.of("user", user, "password", password));
Connection connection = null;
for (DriverInfo driverInfo : registeredDrivers) {
// 第三方厂商会实现java.sql.Driver接口,实现其connect()方法
connection = driverInfo.driver.connect(url, info);
if (connection != null) {
break;
}
}
return connection;
}
// 注册JDBC驱动
public static void registerDriver(Driver driver) {
registeredDrivers.addIfAbsent(new DriverInfo(driver));
}
// 注销JDBC驱动
public static void deregisterDriver(Driver driver) {
DriverInfo driverInfo = new DriverInfo(driver);
synchronized (lockForInitDrivers) {
if (registeredDrivers.contains(driverInfo)) {
registeredDrivers.remove(driverInfo);
}
}
}
}
带着刚才的疑问,进入ServiceLoader
的源码一探究竟吧。盯着load()
方法看了许久,哥陷入深深的沉思:这玩意儿就是单纯地返回一个ServiceLoader实例而已,并没有Class.forName()
的身影啊,莫非上述空的迭代逻辑才是真正用来执行类加载的吗?iterator()
方法生成了一个LazyClassPathLookupIterator
迭代器,也许玄机就在这个迭代器中。
public final class ServiceLoader<S> implements Iterable<S> {
private final Class<S> service;
private final String serviceName;
private final ClassLoader loader;
private Iterator<ServiceLoader.Provider<S>> lookupIterator1;
private final List<S> instantiatedProviders = new ArrayList<>();
public static interface Provider<S> extends Supplier<S> {
Class<? extends S> type();
@Override S get();
}
private ServiceLoader(Class<?> caller, Class<S> svc, ClassLoader cl) {
this.service = svc;
this.serviceName = svc.getName();
this.loader = cl;
}
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return new ServiceLoader<>(Reflection.getCallerClass(), service, cl);
}
@Override
public Iterator<S> iterator() {
if (lookupIterator1 == null) {
Iterator<Provider<S>> iterator = new LazyClassPathLookupIterator<>();
lookupIterator1 = new Iterator<Provider<S>>() {
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public Provider<S> next() {
if (iterator.hasNext()) {
return iterator.next();
} else {
throw new NoSuchElementException();
}
}
};
}
return new Iterator<S>() {
int index;
@Override
public boolean hasNext() {
if (index < instantiatedProviders.size()) {
return true;
}
return lookupIterator1.hasNext();
}
@Override
public S next() {
S next;
if (index < instantiatedProviders.size()) {
next = instantiatedProviders.get(index);
} else {
next = lookupIterator1.next().get();
instantiatedProviders.add(next);
}
index ;
return next;
}
};
}
}
继续跟进,在查阅LazyClassPathLookupIterator
的源码后,脑海中的疑问也随之烟消云散了。nextProviderClass()
无疑是核心逻辑所在,它首先一次性读取SPI配置文件,然后在每一次迭代时通过Class.forName()
方法来加载SPI实现类。
private final class LazyClassPathLookupIterator<T> implements Iterator<Provider<T>> {
static final String PREFIX = "META-INF/services/";
Enumeration<URL> configs;
Iterator<String> pending;
Provider<T> nextProvider;
ServiceConfigurationError nextError;
LazyClassPathLookupIterator() {}
private Class<?> nextProviderClass() {
if (configs == null) {
String fullName = PREFIX service.getName();
configs = loader.getResources(fullName);
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return null;
}
pending = parse(configs.nextElement());
}
String cn = pending.next();
try {
return Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
return null;
}
}
private boolean hasNextService() {
while (nextProvider == null && nextError == null) {
try {
Class<?> clazz = nextProviderClass();
if (clazz == null) {
return false;
}
if (service.isAssignableFrom(clazz)) {
Class<? extends S> type = (Class<? extends S>) clazz;
Constructor<? extends S> ctor = (Constructor<? extends S>)getConstructor(clazz);
ProviderImpl<S> p = new ProviderImpl<S>(service, type, ctor);
nextProvider = (ProviderImpl<T>) p;
} else {
fail(service, clazz.getName() " not a subtype");
}
} catch (ServiceConfigurationError e) {
nextError = e;
}
}
return true;
}
private Provider<T> nextService() {
if (!hasNextService()) {
throw new NoSuchElementException();
}
Provider<T> provider = nextProvider;
if (provider != null) {
nextProvider = null;
return provider;
} else {
ServiceConfigurationError e = nextError;
assert e != null;
nextError = null;
throw e;
}
}
@Override
public boolean hasNext() {
return hasNextService();
}
@Override
public Provider<T> next() {
return nextService();
}
}
实际上,ServiceLoader使用了延迟加载技术,即在需要时才会加载对象或数据;一般,当对象创建的成本非常高且对象的使用非常少时,延迟加载是必不可少的。
2. 入门实战
2.1 json-serializer
2.1.1 定义SPI拓展接口
代码语言:javascript复制package io.github.serializer;
public interface JsonSerializer {
void serialize(Object obj);
}
2.1.2 定义静态工厂类
JsonSerializerManager
是一个静态工厂类,它的构造方法是私有的;getJsonSerializer()
静态方法可以根据特定厂商名称来获取相应的JsonSerializer实例。
package io.github.serializer;
public class JsonSerializerManager {
private static final CopyOnWriteArrayList<JsonSerializerWrapper> REGISTERED_JSON_SERIALIZER = new CopyOnWriteArrayList<>();
private static final AtomicBoolean IS_INITIALIZED = new AtomicBoolean(false);
private JsonSerializerManager() {}
public static void registerJsonSerializer(JsonSerializerWrapper jsonSerializer) {
REGISTERED_JSON_SERIALIZER.addIfAbsent(jsonSerializer);
}
public static void deregisterJsonSerializer(JsonSerializerWrapper jsonSerializer) {
REGISTERED_JSON_SERIALIZER.remove(jsonSerializer);
}
public static JsonSerializer getJsonSerializer(String manufactureName) {
if (!IS_INITIALIZED.get()) {
ServiceLoader<JsonSerializer> jsonSerializerServiceLoader = ServiceLoader.load(JsonSerializer.class);
Iterator<JsonSerializer> jsonSerializerIterator = jsonSerializerServiceLoader.iterator();
while (jsonSerializerIterator.hasNext()) {
jsonSerializerIterator.next();
}
}
return REGISTERED_JSON_SERIALIZER
.stream()
.collect(Collectors.toMap(
JsonSerializerWrapper::getManufactureName,
JsonSerializerWrapper::getJsonSerializer)
)
.get(manufactureName);
}
}
JsonSerializerWrapper
主要用于包装JsonSerializer,重点关注equals()
和hashCode()
中的逻辑。
package io.github.serializer;
@Getter
@Setter
public class JsonSerializerWrapper {
private String manufactureName;
private JsonSerializer jsonSerializer;
@Override
public boolean equals(Object other) {
return (other instanceof JsonSerializerWrapper)
&& this.jsonSerializer == ((JsonSerializerWrapper) other).jsonSerializer;
}
@Override
public int hashCode() {
return jsonSerializer.hashCode();
}
}
2.2 alibaba-json-serializer
2.2.1 引入json-serializer依赖
代码语言:javascript复制<dependency>
<groupId>io.github</groupId>
<artifactId>json-serializer</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
2.2.2 实现SPI拓展接口
代码语言:javascript复制package com.alibaba.json.serializer.privider;
public class AlibabaFastjsonSerializer implements JsonSerializer {
static {
JsonSerializerWrapper wrapper = new JsonSerializerWrapper();
wrapper.setManufactureName("alibaba");
wrapper.setJsonSerializer(new AlibabaFastjsonSerializer());
JsonSerializerManager.registerJsonSerializer(wrapper);
}
@Override
public void serialize(Object obj) {
System.out.println("Alibaba提供的json序列化方案:fastjson");
}
}
2.2.3 编写SPI配置文件
代码语言:javascript复制com.alibaba.json.serializer.privider.AlibabaFastjsonSerializer
2.3 fasterxml-json-serializer
2.3.1 引入json-serializer依赖
代码语言:javascript复制<dependency>
<groupId>io.github</groupId>
<artifactId>json-serializer</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
2.3.2 实现SPI拓展接口
代码语言:javascript复制package org.fasterxml.json.serializer.privider;
public class FasterxmlJacksonSerializer implements JsonSerializer {
static {
JsonSerializerWrapper wrapper = new JsonSerializerWrapper();
wrapper.setManufactureName("fasterxml");
wrapper.setJsonSerializer(new FasterxmlJacksonSerializer());
JsonSerializerManager.registerJsonSerializer(wrapper);
}
@Override
public void serialize(Object obj) {
System.out.println("Fasterxml提供的json序列化方案:jackson");
}
}
2.3.3 编写SPI配置文件
代码语言:javascript复制org.fasterxml.json.serializer.privider.FasterxmlJacksonSerializer
2.4 json-serializer-app
2.4.1 引入第三方SPI实现类的依赖
代码语言:javascript复制<dependency>
<groupId>com.alibaba</groupId>
<artifactId>json-serializer</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.fasterxml</groupId>
<artifactId>json-serializer</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
2.4.2 运行
代码语言:javascript复制public class JsonSerializerApp {
public static void main(String[] args) {
JsonSerializer jsonSerializer = JsonSerializerManager.getJsonSerializer("alibaba");
jsonSerializer.serialize(new Object());
jsonSerializer = JsonSerializerManager.getJsonSerializer("fasterxml");
jsonSerializer.serialize(new Object());
}
}
运行结果如下所示:
代码语言:javascript复制Alibaba提供的json序列化方案:fastjson
Fasterxml提供的json序列化方案:jackson
3. 总结
SPI的确很简单,但如何更优雅地设计与应用呢?个人觉得JDK中java.sql.DriverManager
是一个很好的参照。此外,Java SPI机制有一个较为明显的缺点:无法按需加载指定第三方SPI实现类!!!