从策略模式到 SPI 机制

策略模式体现为 封装一系列的算法以供调用,使这些算法可以解耦独立于主业务之外而存在。而 SPI 扩展机制是 解耦程度更高 的策略模式。

策略模式

定义一种算法:

/**
* 手机
*/
interface Phone {
void call();
}

两个算法实现:

/**
* 魅族手机
*/
@Slf4j
class MeizuPhone implements Phone {
@Override
public void call() {
log.debug("No love, just fxxk off~");
}
}
/**
* 小米手机
*/
@Slf4j
public class MIPhone implements Phone {
@Override
public void call() {
log.debug("Are you OK?");
}
}

再来个工厂:

class PhoneFactory {
private Map<String, Phone> map = new HashMap<>();

private void init() {
map.put("MI", new MIPhone());
map.put("Meizu", new MeizuPhone());
}

public Phone getPhone(String type) {
return map.get(type);
}
}

之后在业务中就可以避免大坨的 If-Else 代码了:

PhoneFactory.getPhone("MI").call();

策略模式理解起来很简单,就是“封装逻辑”。

SPI 扩展机制

思考这样一个问题:如果我要换魅族手机,可能需要修改源码,这样毕竟不灵活,JDK 提供了一种 spi 扩展机制,开发者可以通过 java.util.ServiceLoader 的 load 方法获取 classpath:META-INF/services 下的信息,约定该文件夹下放入 spi 接口全类名的文件,如 com.cdf.spi.Phone,文件内容是本工程打包后对应的 spi 实现,比如 com.cdf.phone.MeizuPhone,具体我们看下例子:

MeizuPhone provider

MIPhone provider

只需要改变当前工程的依赖,由 meizu 换成 xiaomi:

<dependency>
<groupId>com.cdf</groupId>
<artifactId>meizu</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.cdf</groupId>
<artifactId>xiaomi</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

即可改变程序中使用的具体实现:

ServiceLoader<Phone> phones = ServiceLoader.load(Phone.class);
for (Phone phone : phones) {
phone.call();
}

result

这样的好处是,那些被封装的算法们又对业务本身更隔离了,在更改实现是不用一个个去找“Meizu”换成“MI”,spi 可以用在日志框架、JDBC 连接池等通用依赖中。

JDBC 以前的规范是需要通过 Class.forName() 手动加载类的,而新的 DriverManager 使用 SPI 实现,并不用开发者手动去加载驱动。