SPI 全称为 (Service Provider Interface) ,是Java 1.6之后内置的一种服务提供发现机制。SPI可以通过配置来替换服务(或者说interface)的实现;比如java.sql.Driver接口,可以很轻松的从MySQL切换到MongoDB实现。
问题的核心在于,如何根据interface查找对应的实现。
SPI的实现
Java 1.6中,开发者只需要在META-INF/services下添加文件,即可切换、修改对应interface的实现。文件名为实现的接口,文件内容为N个实现的类名,按行分隔。比如:
1 2 3
| $ cat src/main/resources/META-INF/services/com.robberphex.Plugin com.robberphex.impl.PluginImpl1 com.robberphex.impl.PluginImpl2
|
使用起来,主要就是ServiceLoader类:
1 2 3 4
| ServiceLoader<Plugin> loader = ServiceLoader.load(Plugin.class); for (Plugin plugin : loader) { System.out.println(plugin.getClass().getCanonicalName()); }
|
要注意的是,ServiceLoader#load方法是CallerSensitive的,即load的时候用的是调用者的类加载器。
Java 9
Java 9之后,由于模块化的引入,所以SPI机制也做了扩展:除了原有的通过META-INF/services来注册服务外,还可以通过module中的provides…with语句来注册服务:
module-info.java1 2 3 4 5
| module com.robberphex.impl1 { requires com.robberphex.interfacex; exports com.robberphex.impl1; provides com.robberphex.Plugin with PluginImpl1; }
|
Java9之前,Service Provider必须是实现了service接口的具体的类。而Java9开始,Service Provider不必是具体类,而可以是接口,只是这个接口必须具有一个名为provide的static方法,并且该方法必须返回实现特定service接口的类的实例:
PluginProvider.java1 2 3 4 5
| public interface PluginProvider { static Plugin provider() { return new PluginImpl2(); } }
|
module-info.java1 2 3 4 5
| module com.robberphex.impl2 { requires com.robberphex.interfacex; exports com.robberphex.impl2; provides com.robberphex.Plugin with PluginProvider; }
|
详细的例子可以参考Github。
参考资料:
- https://zhuanlan.zhihu.com/p/31767780
- https://openjdk.java.net/projects/jigsaw/quick-start
- https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html
- https://openjdk.java.net/projects/jigsaw/spec/