文章目录
Pre
1. Java SPI 概述
SPI(Service Provider Interface)是Java的一种机制,用于实现服务的动态发现和加载。SPI允许开发者在不修改应用程序代码的情况下,通过配置文件动态地为接口添加不同的实现。
SPI的典型应用场景
- 插件机制:允许第三方开发者扩展功能。
- 动态加载模块:根据需求加载不同模块或功能。
- 解耦合:通过接口和实现的分离,使得代码更加模块化和灵活。
2. SPI 基本用法
2.1 创建一个SPI接口
首先,我们创建一个简单的SPI接口。例如,我们定义一个RpcAccessPoint
接口:
package com.artisan.rpc;
import java.io.Closeable;
import java.net.URI;
public interface RpcAccessPoint extends Closeable {
/**
* 客户端获取远程服务的引用
* @param uri 远程服务地址
* @param serviceClass 服务的接口类的Class
* @param <T> 服务接口的类型
* @return 远程服务的引用
*/
<T> T getRemoteService(URI uri , Class<T> serviceClass);
/**
* 向远程服务添加服务提供者
* @param service 实现实例
* @param serviceClass
* @param <T> 服务接口的类型
* @return 远程服务的地址
*/
<T> URI addServiceProvider(T service , Class<T> serviceClass);
/**
* 服务端启动RPC框架,监听接口,开始提供远程服务。
* @return 服务实例,用于程序停止的时候安全关闭服务。
*/
Closeable startServer() throws Exception ;
}
2.2 创建多个SPI实现
接下来,我们创建两个不同的实现类,例如RpcAccessPointImpl
和RpcAccessPointImpl2
:
package com.artisan.rpc.impl;
import com.artisan.rpc.RpcAccessPoint;
import com.artisan.rpc.spi.Singleton;
import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
/**
* @author 小工匠
* @version 1.0
* @description: TODO
* @date 2024/8/26 10:11
* @mark: show me the code , change the world
*/
@Singleton
public class RpcAccessPointImpl implements RpcAccessPoint {
@Override
public <T> T getRemoteService(URI uri, Class<T> serviceClass) {
System.out.println("getRemoteService called");
return null;
}
@Override
public <T> URI addServiceProvider(T service, Class<T> serviceClass) {
return null;
}
@Override
public Closeable startServer() throws Exception {
return null;
}
@Override
public void close() throws IOException {
}
}
package com.artisan.rpc.impl;
import com.artisan.rpc.RpcAccessPoint;
import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
/**
* @author 小工匠
* @version 1.0
* @description: TODO
* @date 2024/8/26 10:11
* @mark: show me the code , change the world
*/
public class RpcAccessPointImpl2 implements RpcAccessPoint {
@Override
public <T> T getRemoteService(URI uri, Class<T> serviceClass) {
return null;
}
@Override
public <T> URI addServiceProvider(T service, Class<T> serviceClass) {
return null;
}
@Override
public Closeable startServer() throws Exception {
return null;
}
@Override
public void close() throws IOException {
}
}
2.3 在META-INF/services
中配置服务提供者
为让Java知道这些实现类,我们需要在META-INF/services
目录下创建一个文件。文件名应与SPI接口的完全限定名一致,例如:META-INF/services/com.artisan.rpc.RpcAccessPoint
。
文件内容列出所有实现类的完全限定名:
com.artisan.rpc.impl.RpcAccessPointImpl
com.artisan.rpc.impl.RpcAccessPointImpl2
com.artisan.rpc.impl.RpcAccessPointImpl3
2.4 使用ServiceLoader
加载和使用服务实现
现在,我们可以使用ServiceLoader
来加载这些服务实现:
package com.artisan.rpc.spi;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
/**
* 提供服务加载功能的支持类,特别是处理单例服务
* @author artisan
*/
public class ServiceSupport {
/**
* 存储单例服务的映射,确保每个服务只有一个实例
*/
private final static Map<String, Object> singletonServices = new HashMap<>();
/**
* 加载单例服务实例
* 本方法通过ServiceLoader加载服务,并确保加载的是单例服务实例
* 如果无法找到对应的服务实例,将抛出ServiceLoadException
*
* @param service 服务类的Class对象,用于指定需要加载的服务类型
* @param <S> 服务类的类型参数,表示服务类型的泛型
* @return 单例服务实例,返回指定类型的单例服务对象
* @throws ServiceLoadException 如果找不到服务实例,将抛出此自定义异常
*/
public synchronized static <S> S load(Class<S> service) {
// 使用ServiceLoader加载服务提供者,并通过stream进行处理
// 调用ServiceSupport中的singletonFilter方法对服务实例进行过滤,确保是单例实例
// findFirst方法找到第一个符合条件的服务实例
// 如果没有找到服务实例,则抛出ServiceLoadException异常
return StreamSupport.
stream(ServiceLoader.load(service).spliterator(), false)
.map(ServiceSupport::singletonFilter)
.findFirst().orElseThrow(ServiceLoadException::new);
}
/**
* 加载所有服务实例
*
* @param service 服务类的Class对象
* @param <S> 服务类的类型参数
* @return 所有服务实例的集合
*/
public synchronized static <S> Collection<S> loadAll(Class<S> service) {
// 使用ServiceLoader加载服务实例,并通过stream进行处理
// 使用ServiceSupport的singletonFilter方法筛选服务实例
// 最终将筛选后的服务实例收集到一个集合中
return StreamSupport.
stream(ServiceLoader.load(service).spliterator(), false)
.map(ServiceSupport::singletonFilter).collect(Collectors.toList());
}
/**
* 对服务实例进行单例过滤
*
* @param service 服务实例
* @param <S> 服务类的类型参数
* @return 单例过滤后的服务实例,如果该服务是单例的并且已有实例存在,则返回已存在的实例
*/
@SuppressWarnings("unchecked")
private static <S> S singletonFilter(S service) {
// 检查服务类是否被标记为单例
if(service.getClass().isAnnotationPresent(Singleton.class)) {
// 获取服务类的全限定名
String className = service.getClass().getCanonicalName();
// 使用Double-checked locking原则,确保只有一个实例
Object singletonInstance = singletonServices.putIfAbsent(className, service);
// 如果之前没有实例存在,则返回当前实例;否则返回已存在的实例
return singletonInstance == null ? service : (S) singletonInstance;
} else {
// 如果不是单例,则直接返回服务实例
return service;
}
}
}
所有在
META-INF/services
中配置的RpcAccessPoint
实现都会被实例化和调用。
ServiceSupport
类负责加载服务实例。它使用Java的ServiceLoader机制来加载服务实现,并对这些实现进行单例检查和过滤.
在ServiceSupport
类中,singletonFilter
方法用于检查服务实例是否是单例的。如果服务类上标注了@Singleton
注解,则确保返回的是同一个实例
package com.artisan.rpc.spi;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Singleton {
}
测试
import com.artisan.rpc.RpcAccessPoint;
import com.artisan.rpc.spi.ServiceSupport;
import org.testng.annotations.Test;
import java.util.Collection;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
@Test
public class SpiTest {
@Test
public void testSpi() {
RpcAccessPoint rpcAccessPoint = ServiceSupport.load(RpcAccessPoint.class);
System.out.println(rpcAccessPoint);
// 测试是否是单例 看输出的内存地址
rpcAccessPoint = ServiceSupport.load(RpcAccessPoint.class);
System.out.println(rpcAccessPoint);
// 自行方法调用
rpcAccessPoint.getRemoteService(null, null);
System.out.println("===============");
Collection<RpcAccessPoint> rpcAccessPoints = ServiceSupport.loadAll(RpcAccessPoint.class);
rpcAccessPoints.forEach(System.out::println);
rpcAccessPoints = ServiceSupport.loadAll(RpcAccessPoint.class);
rpcAccessPoints.forEach(System.out::println);
}
}
通过SPI机制,项目可以实现服务的动态加载和插件化。ServiceSupport
类作为服务加载的核心组件,使用ServiceLoader
机制加载服务实现,并对这些实现进行单例检查和过滤。
这种机制使得项目具有良好的扩展性和灵活性,可以方便地添加新的服务提供者实现。
3. SPI与设计模式的结合
3.1 SPI与工厂模式结合
工厂模式常用于创建对象的实例。当结合SPI时,工厂类可以动态地选择服务实现,增强系统的灵活性。例如:
public interface PaymentServiceFactory {
PaymentService createPaymentService();
}
通过SPI机制,我们可以有多个PaymentServiceFactory
实现。使用ServiceLoader
加载合适的工厂类来创建对象:
public class PaymentServiceFactoryLoader {
public static PaymentService loadPaymentService() {
ServiceLoader<PaymentServiceFactory> loader = ServiceLoader.load(PaymentServiceFactory.class);
return loader.iterator().next().createPaymentService();
}
}
这种方式将工厂模式与SPI结合,使得在不修改代码的情况下添加新工厂实现变得更加容易。
3.2 SPI与策略模式结合
策略模式允许在运行时选择算法或行为。通过SPI,可以动态加载策略,实现行为的灵活切换。例如:
public interface CompressionStrategy {
void compress(String data);
}
可以通过SPI加载不同的压缩策略:
public class CompressionContext {
private CompressionStrategy strategy;
public CompressionContext() {
ServiceLoader<CompressionStrategy> loader = ServiceLoader.load(CompressionStrategy.class);
this.strategy = loader.iterator().next();
}
public void compressData(String data) {
strategy.compress(data);
}
}
3.3 SPI与观察者模式结合
SPI结合观察者模式可以用于实现事件驱动的架构。通过SPI动态加载观察者,实现对特定事件的响应。例如:
public interface EventListener {
void onEvent(Event event);
}
在事件发生时,使用ServiceLoader
加载并通知所有观察者:
public class EventManager {
public void publishEvent(Event event) {
ServiceLoader<EventListener> loader = ServiceLoader.load(EventListener.class);
for (EventListener listener : loader) {
listener.onEvent(event);
}
}
}
这种实现方式适用于需要动态添加或更改事件处理逻辑的场景。
34 SPI与装饰器模式结合
装饰器模式用于在不修改原有对象的情况下动态地为其增加功能。结合SPI,可以在运行时动态加载装饰器。例如:
public interface MessageService {
String sendMessage(String message);
}
装饰器可以通过SPI动态加载并叠加:
public class MessageServiceDecorator implements MessageService {
private MessageService delegate;
public MessageServiceDecorator(MessageService delegate) {
this.delegate = delegate;
}
@Override
public String sendMessage(String message) {
// 增加一些功能
return delegate.sendMessage(message) + " [Decorated]";
}
}
通过ServiceLoader
加载并应用多个装饰器:
public class DecoratorLoader {
public static MessageService loadDecoratedService() {
ServiceLoader<MessageServiceDecorator> loader = ServiceLoader.load(MessageServiceDecorator.class);
MessageService service = new BaseMessageService();
for (MessageServiceDecorator decorator : loader) {
service = new MessageServiceDecorator(service);
}
return service;
}
}
具体应用案例:构建可扩展的日志框架
我们将实现一个可扩展的日志框架,通过SPI和工厂模式实现动态日志处理,并通过装饰器模式增加日志功能。
定义日志接口
public interface Logger {
void log(String message);
}
实现基本的日志类
例如,控制台日志和文件日志:
public class ConsoleLogger implements Logger {
@Override
public void log(String message) {
System.out.println("Console Logger: " + message);
}
}
public class FileLogger implements Logger {
@Override
public void log(String message) {
// 简化:模拟文件日志
System.out.println("File Logger: " + message);
}
}
配置SPI
在META-INF/services/com.example.Logger
中列出所有日志实现:
com.example.ConsoleLogger
com.example.FileLogger
使用工厂模式加载日志器
public class LoggerFactory {
public static Logger getLogger() {
ServiceLoader<Logger> loader = ServiceLoader.load(Logger.class);
return loader.iterator().next(); // 返回第一个日志器
}
}
添加装饰器
public class TimestampLoggerDecorator implements Logger {
private Logger logger;
public TimestampLoggerDecorator(Logger logger) {
this.logger = logger;
}
@Override
public void log(String message) {
logger.log("[" + System.currentTimeMillis() + "] " + message);
}
}
使用装饰器:
public class LoggerFactoryWithDecorator {
public static Logger getLogger() {
ServiceLoader<Logger> loader = ServiceLoader.load(Logger.class);
Logger logger = loader.iterator().next();
return new TimestampLoggerDecorator(logger);
}
}
使用日志框架
public class Application {
public static void main(String[] args) {
Logger logger = LoggerFactoryWithDecorator.getLogger();
logger.log("Application started");
}
}
高级功能:动态切换日志实现
通过外部配置文件或系统属性,动态选择加载不同的日志实现。
public class LoggerFactoryWithDynamicSelection {
public static Logger getLogger(String loggerType) {
ServiceLoader<Logger> loader = ServiceLoader.load(Logger.class);
for (Logger logger : loader) {
if (logger.getClass().getSimpleName().equalsIgnoreCase(loggerType)) {
return new TimestampLoggerDecorator(logger);
}
}
throw new IllegalArgumentException("No suitable logger found for type: " + loggerType);
}
}