本文将通过Spring SPI的案例,给大家介绍如何设计一个简单但又强大的SPI扩展机制。
SPI(Service Provider Interface)是一种常用的扩展机制,它通过不改变原有系统的情况下,允许添加新的功能模块。Spring就是利用SPI实现了许多可配置和可替换的设计,比如动态代理,资源加载等功能通过SPI进行扩展。
我们以一个简单的RPC调用接口作为案例,来展示Spring中的SPI设计思路:
代码语言:java复制public interface RpcCall {
Object call(String url, String method, Object[] args);
}
这个RpcCall接口定义了远程调用的常规操作,我们来看看如何通过SPI来实现不同协议(如HTTP、TCP等)的调用扩展:
代码语言:java复制// RpcCall的默认实现
public class DefaultRpcCall implements RpcCall {
@Override
public Object call(String url, String method, Object[] args) {
// 具体调用实现
}
}
定义一个默认实现类,这是SPI不可或缺的一部分。我们通过环境变量配置使用哪个实现:
代码语言:java复制String implementation =
System.getenv("rpc.implementation");
RpcCall call;
if(implementation == null){
call = new DefaultRpcCall();
}else{
call = loadImplementation();
}
//方法调用
call.call("http://localhost:8080","hello",new Object[] {});
loadImplementation方法负责通过反射或其他方式加载配置的实现类:
代码语言:java复制private RpcCall loadImplementation(){
try{
Class clazz = Class.forName(implementation);
return (RpcCall) clazz.getDeclaredConstructor().newInstance();
}catch(Exception e){
return new DefaultRpcCall();
}
}
接下来,我们可以很容易地实现HTTP和TCP两种调用协议:
代码语言:java复制// HTTP 实现
public class HttpRpcCall implements RpcCall{
@Override
public Object call(String url, String method, Object[] args){
// HTTP 实现代码
}
}
// TCP 实现
public class TcpRpcCall implements RpcCall{
@Override
public Object call(String url, String method, Object[] args){
// TCP 实现代码
}
}
通过设置环境变量"rpc.implementation=com.demo.HttpRpcCall"或"rpc.implementation=com.demo.TcpRpcCall",我们就可以一键切换调用实现而无须修改任何调用代码。
这个架构同样适用于Spring中许多组件的扩展,例如AuthorizationManager、HandlerMethodArgumentResolver等。我们可以通过配置文件或代码方式很方便地切换实现类。
与服务提供者模型(Service Provider Model)相比,SPI能更好地支持热插拔和零配置。开发者也无需修改调用代码就可以扩展新的功能。这给系统架构带来了很好的灵活性。
所以,在设计可扩展组件时,使用SPI提供的接口和默认实现可以帮助我们快速搭建出一个“开放-关闭”和“可配置”的系统框架。这也是Spring之所以如此流行的一个重要原因。
总结来说:
- 定义一个标准接口和一个默认实现作为SPI的基础
- 通过配置从 SPI 中动态加载完整的实现类
- 实现类实例通过接口进行调用操作
- 实现无侵入性的拓展能力
当然,SPI还有一些缺点,比如行为不一致、难以升级等。但对于需要动态扩展能力的系统来说,它提供了一种非常简单实用的解决方案。
我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!