SPI的全称Service Provider Interface.简单来说就是为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要.

乱叙

将近一年没有更新博客,越来越懒了! 这一年分别折腾了kafka、druid、hawq、dubbo、flume等等,但基本都是浅尝辄止。其中kafka、dubbo、flume已经应用到生产环境中,最近准备尝试下GreenPlum

对于SPI,是最近在看dubbo源码时了解到其强大的扩展机制正是基于JAVA SPI来实现的。说来惭愧,因为dubbo才知道的SPI。 感兴趣的话,只需几分钟你便能学会如何使用SPI.参考Java spi机制浅谈

一点自己的认识

面向对象程序设计原则提倡接口隔离原则以及开闭原则,在实际编程中大多人也是这么做的。如:
spi

调用:

1
EchoService echoServie = new SimpleEchoServiceImpl();

看起来貌似还不错,随着系统不断扩展,该service可能会被很多模块调用。顺理成章,将该service抽离成一个单独的第三方模块。一段时间之后,考虑到SimpleEchoServiceImpl貌似有点丑陋或者说性能不够好,同时考虑到兼容,所以你实现了PrettyEchoServiceImpl。现在你想让调用EchoService服务的client都换成PrettyEchoServiceImpl的实现。很不幸,当你统计之后发现,很多其他模块已经将实现方式固定了,也就是前面的代码片段,不得不修改成

1
EchoService echoServie = new PrettyEchoServiceImpl();

这时回头发现,遵循接口隔离原则以及开闭原则似乎在这种模块引用情形下,有些无能无力。对于,服务提供者希望做到各种迭代不至于影响服务调用者,变化只存在于内部即可。而对于服务调用者来说,不想去关心服务提供方具体的实现方式。在我看来,SPI就是为解决这种问题而生的,那么作为服务提供方该如何做呢?如下所示:
spi-demo

对于服务调用者只需在maven pom中引入demo-0.0.1-SNAPSHOT.jar,在程序中通过如下方式调用EchoService

1
2
3
4
5
6
ServiceLoader<EchoService> serviceLoader = ServiceLoader.load(EchoService.class);
Iterator<EchoService> echoServices = serviceLoader.iterator();
while(echoServices.hasNext()){
EchoService echoService = echoServices.next();
System.out.println(echoService.echo("hello world"));
}

这样,调用者便与具体实现进行了隔离。服务升级,调用方只需更新demo.jar即可,无需修改任何代码!

总结

总体来看Java SPI就是提供了模块隔离机制! 其实JDBC的各种数据库驱动Driver也是这么实现的.以下java.sql.DriverManager中的一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public Void run() {
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator driversIterator = loadedDrivers.iterator();
/* Load these drivers, so that they can be instantiated.
* It may be the case that the driver class may not be there
* i.e. there may be a packaged driver with the service class
* as implementation of java.sql.Driver but the actual class
* may be missing. In that case a java.util.ServiceConfigurationError
* will be thrown at runtime by the VM trying to locate
* and load the service.
*
* Adding a try catch block to catch those runtime errors
* if driver not available in classpath but it's
* packaged as service and that service is there in classpath.
*/
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}

Comments