目录
引言
面向接口编程要求我们先根据业务抽象出一个接口,然后根据具体规则建立不同的实现类, 这样一个接口可能会有多个实现类。在具体调用时,就需要指定对应的实现类,当需求发生变化是时,比如新增一个实现或者已有的实现类已经过时,就需要对服务调用端代码进行变更,这就导致了侵入性。
举个例子说明一下,比如一项业务需要根据调用者的需求而使用不同的协议进行处理,那么首先建立一个Protocol接口,他具有两个方法,一个用于获取协议类型,一个进行执行调用:
package com.ryan.myspi;
public interface Protocol {
// 获取协议类型
String getProtocolType();
// 执行调用
void invoke();
}
第一期根据有两个实现类,一个使用Dubbo协议,另一个使用Hessian协议,写两个实现类:
Dubbo协议实现类DubboProtocol:
public class DubboProtocol implements Protocol {
private static final String protocolType = "dubbo";
@Override
public String getProtocolType() {
return protocolType;
}
@Override
public void invoke() {
System.out.println ("this is dubbo protocol ");
}
}
Dubbo协议实现类HessianProtocol:
public class HessianProtocol implements Protocol {
private static final String protocolType = "hessian";
@Override
public String getProtocolType() {
return protocolType;
}
@Override
public void invoke() {
System.out.println ("this is hessian protocol ");
}
}
接下类,建立客户端调用类,首先把系统中的实现类加载到缓存中,再调用时,根据调用端需求判断使用哪一个协议实现类:
public class ProtocolClient {
private final static Map<String,Protocol> RPOTOCOL_LIST = new HashMap<> ();
static {
RPOTOCOL_LIST.put ("dubbo", new DubboProtocol ());
RPOTOCOL_LIST.put ("hessian", new HessianProtocol ());
}
public void doInvoke(String protocolType) {
Protocol protocol = ProtocolClient.RPOTOCOL_LIST.get (protocolType);
if (protocol != null) {
protocol.invoke ();
}
}
}
大家想一下,如果以后需求发生了变化,比如需要根据http协议新增一个HttpProtocol实现类同时不再支持DubboProtocol协议,那么就需要侵入ProtocolClient的static中的代码块。如果需要多个协议进行处理时,比如一个请求需要经过一个协议链,那么维护将更加复杂。
我们需要一种方式消除调用者对实现类的依赖,最好可以在配置中对实现类(也可以称为组件)进行动态的添加和替换,这时候*SPI(Service Provider Interface)*便闪亮登场了。
SPI实现
SPI通过在META-INF/services 中增加一个配置文件,该文件名使用服务接口全限定名(the fully-qualified binary name of the service’s type)表示,其中内容包含一系列服务提供者类的全限定名(a list of fully-qualified binary names of concrete provider classes, one per line).
看了上面这段想必一定很晕吧,接下来对之前的例子进行重构来看一下SPI的实现:
还是两个Protocol接口的实现类DubboProtocol大哥和HessianProtocol小弟,把这两兄弟放到配置文件中,文件名为com.ryan.myspi.Protocol中,实现了这步后整个文件目录如下:
├─main
│ ├─java
│ │ └─com
│ │ └─ryan
│ │ └─myspi
│ │ DubboProtocol.java
│ │ HessianProtocol.java
│ │ Protocol.java
│ │
│ └─resources
│ └─META-INF
│ └─services
│ com.ryan.myspi.Protocol
│
在com.ryan.myspi.Protocol配置文件中添加两个实现类的全限定名:
com.ryan.myspi.DubboProtocol
com.ryan.myspi.HessianProtocol
接下来在调用客户端中,替换掉原静态代码块中的内容,使用ServiceLoader来加载实现类
public class SpiProtocolClient {
private final static Map<String,Protocol> RPOTOCOL_LIST = new HashMap<> ();
static {
// 使用ServiceLoader
ServiceLoader<Protocol> serviceLoader = ServiceLoader.load (Protocol.class);
Iterator<Protocol> iterator = serviceLoader.iterator ();
while (iterator.hasNext ()) {
Protocol itr = iterator.next ();
RPOTOCOL_LIST.put (itr.getProtocolType (), itr);
}
}
/**
* 描述:调用相关协议
* @since JDK 1.8
*/
public void doInvoke(String protocolType) {
Protocol protocol = SpiProtocolClient.RPOTOCOL_LIST.get (protocolType);
if (protocol != null) {
protocol.invoke ();
}
}
}
可以看到通过使用SPI,以后需要增加协议实现类,或者替换已有的协议实现类时,只要对com.ryan.myspi.Protocol文件进行修改即可。
小结
本文简单介绍了下SPI的使用,当然JDK中的SPI还有一些缺陷,比如会对所有的实现类进行初始化并加载,如果没用的类比较多的话,会很耗资源并且启动时间也会很久。因此Dubbo对SPI进行了加强,接下来会做进一步分析。
如果需要原文代码的朋友请关注公众号,并在后台回复:spi1
推荐阅读:
了解更多请关注微信公众号