Java SPI的全称是Java Service Provider Interface ,翻译成中文的意思就服务提供接口。它是为了解决接口实现方与调用方分离的。
在面向对象的设计里我们一般推荐面向接口编程。
在多个模块之间不对实现进行硬编码,如果替换一种实现实现方式就会重新修改代码。可能有人会说Spring 支持一个接口多个实现类。在注入Bean的时候只要使用@Qualifier注解指明我们所要使用的实现类不就可以了吗?当然这么做是没有错的!但是并不是所有的软件产品依赖了Spring。这时候我们如果硬编码就会无法做到很好的扩展性,比如你要写一个Rpc的矿建,对于它的序列化方案,你肯定不想只使用一种方式,但是如果有一天有几个新的第三方想要扩展新的序列化方案。你肯定也不想它修改你的原有代码,重新实现接口然后创建实例,下次要换别的你就又要修改代码。
如果我的Rpc框架不用关心别人怎么实现的序列化的只是调用接口就可以,无需关注别人的实现,那就不用总是改代码了。这时候大家就会想:没有实现类怎么调用方法,Java 中的方法的调用都必须实例化。也许这时候有人会说:JDK的动态代理不就可以创建一个接口的实现类. 但是你别忘了它还是依赖一个真实对象的引用。
public class AnimalInvocationHandle<T> implements InvocationHandler {
private Object realObject;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object object = method.invoke(realObject,args);
return object;
}
}
newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h);
在jdk6里面引进的一个新的特性ServiceLoader,而且ServiceLoader可以通过service provider的配置文件来装载指定的service provider。当服务的提供者,提供了服务接口的一种实现之后,我们只需要在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
Spi Hello World
下面我们就用一个例子来展开
我们定义了一个接口HelloService,然后再resources目录下创建META-INF/services/目录然后创建一个名为我们Service注解全限定名文件:
com.clown.spi.HelloService 内容暂时为空。
public interface HelloService {
void printHello();
}
打印Hello 我们这里呢实现两个接口一个打印中文的Hello 也就是“你好”,另一个打印英文的“Hello”
public class SayChineseHello implements HelloService {
@Override
public void printHello() {
System.out.println("你好");
}
}
public class SayEnglishHello implements HelloService {
@Override
public void printHello() {
System.out.println("hello");
}
}
我们通过ServiceLoad去调用它:
public class SpiTest {
public static void main(String[] args) {
ServiceLoader<HelloService> serviceLoader= ServiceLoader.load(HelloService.class);
Iterator<HelloService> iterator = serviceLoader.iterator();
while (iterator.hasNext()){
iterator.next().printHello();
}
}
}
这里呢 ,我们还不知道我们实现是哪一个,会打印什么内容。调用方完全不知道底层的实现细节。接下来我们来指定我们所调用的实现:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aiYyWuii-1579244431632)(E5AC2B1D01454C0E878769EB4DFF39EF)]
运行代码,打印结果如下:
你好
SPI的运用
Servlet3.0 规范
ServletContainerInitializer 类通过jar services API 查找。对于每一个应用,应用启动时,由容器创建一个
ServletContainerInitializer 实例。框架提供的ServletContainerInitializer 实现必须绑定在jar 包的
META-INF/services 目录中的一个叫做javax.servlet.ServletContainerInitializer的文件,根据jar services API,
指定ServletContainerInitializer 的实现。
这部分的内容: 可以看我 ServletContainerInitializer-- Servlet3.1 无XML启动SpringMVC
Dubbo
Dubbo对自己对SPI 进行了优化,而且提供了注解@SPI等,对SPI的支持。详见Dubbo源码,《深入理解Apache Dubbo与实战》