浅谈 java spi 如何帮助框架插件化

spi 的全称是Service Provider Interface,主要作用是在让服务具备运行时加载接口的指定实现类的能力,java从 1.6 开始提供此机制(其实 1.3 开始就有了,只不过一直自嗨内部使用,没暴露外部方法给大家用而已),而各种框架有时也自己实现此机制以增强一些特有的功能(e.g:dubbo自己实现的 spi,spring-boot 类似的有spring factories)。
spi通常应用在框架中,辅助框架实现功能的插件化,让用户自己按照约定写的功能也能被框架加载运行。下面就举个 java spi 的例子,从例子中感受spi 的用法以及如何帮助框架实现插件化

java spi demo

ps:以下 demo 可以自行下载

git clone git@github.com:likemoongg/blog-code-demo.git

为了说明spi 的具体用途,在这里举个例子:为了实现通用的字典查询功能可以开发一种框架,可实现对各种不同字典的查询,字典可以由用户或第三方包定制。

框架侧代码

字典类的接口Dictionary

package dictionary.spi; public interface Dictionary { public String getDefinition(String word); }

查询字典的服务DictionaryService

package dictionary; import dictionary.spi.Dictionary; import java.util.Iterator; import java.util.ServiceConfigurationError; import java.util.ServiceLoader; public class DictionaryService { private static DictionaryService service; private ServiceLoader<Dictionary> loader; private DictionaryService() { // 这里的 ServiceLoader 就是 java 原生 spi 的重要入口类 // 其入参是一个接口,返回的 ServiceLoader<Dictionary> loader 可以加载出其实现类的实例 loader = ServiceLoader.load(Dictionary.class); } // 实现单例 public static synchronized DictionaryService getInstance() { if (service == null) { service = new DictionaryService(); } return service; } // 扫描 目录META-INF/services/dictionary.spi.Dictionary下描述的所有的 Dictionary 的实现类,依次查找入参的 word // 其中目录层级META-INF/services是 java spi 默认约定的目录 public String getDefinition(String word) { String definition = null; try { // 利用 ServiceLoader<Dictionary> loader 的迭代器遍历每一个实现类,寻找可以识别 word的字典 Iterator<Dictionary> dictionaries = loader.iterator(); while (definition == null && dictionaries.hasNext()) { Dictionary d = dictionaries.next(); definition = d.getDefinition(word); } } catch (ServiceConfigurationError serviceError) { definition = null; serviceError.printStackTrace(); } return definition; } }

以上两个类就是框架提供的所有类了,我们可以看到框架还没有提供注释里所说的 META-INF/services/dictionary.spi.Dictionary 文件啊!用户运行起来肯定有问题呀!!
这个目录就是 spi 机制的关键也是我们能实现插件化的重要一步。从上面可以看到框架并没有和具体的某个实现类捆绑起来,而是通过 ServiceLoader 去寻找具体的实现类,接口对应哪些实现类就是 META-INF/services/dictionary.spi.Dictionary 文件所描述的关键信息啦。所以用户可以自己编写该文件,就相当于指定了 Dictionary 的实现类具体有哪些,也就实现了插件化!

用户侧的程序

使用框架的用户自己编写的具体Dictionary实现类有两个,一个汉语词典EnDictionary,一个汉语词典CnDictionary。
那么 META-INF/services/dictionary.spi.Dictionary 可以这么写(其实就是类的全限定名称啦~)

net.likemoon.dictionary.EnDictionary net.likemoon.dictionary.CnDictionary

两个具体实现类如下

package net.likemoon.dictionary; public class EnDictionary implements dictionary.spi.Dictionary{ @Override public String getDefinition(String word){ if (word.matches("[a-zA-Z]+")) { return "looking up English dictionary..."; } return null; } } public class CnDictionary implements Dictionary { @Override public String getDefinition(String word){ if (!word.matches("[a-zA-Z]+")) { return "正在查阅中文字典..."; } return null; } }

最后是完整的用户使用框架的的例子

package net.likemoon; import dictionary.DictionaryService; public class lookupDictionary { public static void main(String[] args) { DictionaryService dictionaryService = DictionaryService.getInstance(); System.out.println(dictionaryService.getDefinition("english")); System.out.println(dictionaryService.getDefinition("中文")); } }

输出

looking up English dictionary... 正在查阅中文字典... 总结

上面的demo 中,框架始终只面向 Dictionary 接口编写功能。具体的字典实现类由java spi 获取。而用户可以在不侵入框架代码的情况下,通过编写约定的描述文件,让框架加载用户自己编写的实现类,扩展功能实现插件化。
当然除了用户(业务程序员)自己编写以外,引入第三方编写的插件 jar 包也可以实现相同的扩展效果。此时第三方的 jar包需包含:扩展的实现类+服务描述文件

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwxdxw.html