Java编程技术之浅析SPI服务发现机制
spi服务发现机制
spi是java jdk内部提供的一种服务发现机制。
-
spi->service provider interface,服务提供接口,是java jdk内置的一种服务发现机制
-
通过在classpath路径下的meta-inf/services文件夹查找文件,自动加载文件里所定义的类
[⚠️注意事项]:
面向对象的设计里,一般推荐模块之间基于接口编程,模块之间不对实现类进行编码。如果涉及实现类就会违反可插拔的原则,针对于模块装配,java spi提供了为某个接口寻找服务的实现机制。
spi规范
- 使用约定:
[1].编写服务提供接口,可以是抽象接口和函数接口,jdk1.8之后推荐使用函数接口
[2].在jar包的meta-inf/services/目录里创建一个以服务接口命名的文件。其实就是实现该服务接口的具体实现类。
提供一个目录: meta-inf/services/ 放到classpath下面
[3].当外部程序装配这个模块的时候,就能通过该jar包meta-inf/services/配置文件找到具体的实现类名,并装载实例化,完成模块注入。
目录下放置一个配置文件: 文件名是需要拓展的接口全限定名称 文件内部为要实现的接口实现类 文件必须为utf-8编码
[4].寻找服务接口实现,不用在代码中提供,而是利用jdk提供服务查找工具类:java.util.serviceloader类来加载使用:
serviceloader.load(xxx.class) serviceloader<xxxinterface> loads = serviceloader.load(xxx.class)
spi源码分析
[1].serviceloader源码:
package java.util; import java.io.bufferedreader; import java.io.ioexception; import java.io.inputstream; import java.io.inputstreamreader; import java.net.url; import java.security.accesscontroller; import java.security.accesscontrolcontext; import java.security.privilegedaction; import java.util.arraylist; import java.util.enumeration; import java.util.iterator; import java.util.list; import java.util.nosuchelementexception; public final class serviceloader<s> implements iterable<s> { //[1].初始化定义全局配置文件路径path private static final string prefix = "meta-inf/services/"; //[2].初始化定义加载的服务类或接口 private final class<s> service; //[3].初始化定义类加载器 private final classloader loader; //[4].初始化定义访问控制上下文 private final accesscontrolcontext acc; //[5].初始化定义加载服务类的缓存集合 private linkedhashmap<string,s> providers = new linkedhashmap<>(); //[6].初始化定义私有内部lazyiterator类,真正加载服务类的实现类 private lazyiterator lookupiterator; //私有化有参构造-> serviceloader(class<s> svc, classloader cl) private serviceloader(class<s> svc, classloader cl) { //[1].实例化服务接口->class<s> service = objects.requirenonnull(svc, "service interface cannot be null"); //[2].实例化类加载器->classloader loader = (cl == null) ? classloader.getsystemclassloader() : cl; //[3].实例化访问控制上下文->accesscontrolcontext acc = (system.getsecuritymanager() != null) ? accesscontroller.getcontext() : null; //[4].回调函数->reload reload(); } public void reload() { //[1].清空缓存实例集合 providers.clear(); //[2].实例化私有内部lazyiterator类->lazyiterator lookupiterator = new lazyiterator(service, loader); } public static <s> serviceloader<s> load(class<s> service,classloader loader) { return new serviceloader<>(service, loader); } public static <s> serviceloader<s> load(class<s> service) { classloader cl = thread.currentthread().getcontextclassloader(); return serviceloader.load(service, cl); } }
2.lazyiterator源码:
private class lazyiterator implements iterator<s> { class<s> service; classloader loader; enumeration<url> configs = null; iterator<string> pending = null; string nextname = null; private lazyiterator(class<s> service, classloader loader) { this.service = service; this.loader = loader; } private boolean hasnextservice() { if (nextname != null) { return true; } if (configs == null) { try { string fullname = prefix + service.getname(); if (loader == null) configs = classloader.getsystemresources(fullname); else configs = loader.getresources(fullname); } catch (ioexception x) { fail(service, "error locating configuration files", x); } } while ((pending == null) || !pending.hasnext()) { if (!configs.hasmoreelements()) { return false; } pending = parse(service, configs.nextelement()); } nextname = pending.next(); return true; } private s nextservice() { if (!hasnextservice()) throw new nosuchelementexception(); string cn = nextname; nextname = null; class<?> c = null; try { c = class.forname(cn, false, loader); } catch (classnotfoundexception x) { fail(service, "provider " + cn + " not found"); } if (!service.isassignablefrom(c)) { fail(service, "provider " + cn + " not a subtype"); } try { s p = service.cast(c.newinstance()); providers.put(cn, p); return p; } catch (throwable x) { fail(service, "provider " + cn + " could not be instantiated", x); } throw new error(); // this cannot happen } public boolean hasnext() { if (acc == null) { return hasnextservice(); } else { privilegedaction<boolean> action = new privilegedaction<boolean>() { public boolean run() { return hasnextservice(); } }; return accesscontroller.doprivileged(action, acc); } } public s next() { if (acc == null) { return nextservice(); } else { privilegedaction<s> action = new privilegedaction<s>() { public s run() { return nextservice(); } }; return accesscontroller.doprivileged(action, acc); } } public void remove() { throw new unsupportedoperationexception(); } }
使用举例
[1].dubbo spi 机制:
meta-inf/dubbo.internal/xxx=接口全限定名
dubbo 并未使用 java spi,而是重新实现了一套功能更强的 spi 机制。
dubbo spi 的相关逻辑被封装在了 extensionloader 类中,通过 extensionloader,我们可以加载指定的实现类。dubbo spi 所需的配置文件需放置在 meta-inf/dubbo 路径下。
与java spi 实现类配置不同,dubbo spi 是通过键值对的方式进行配置,这样我们可以按需加载指定的实现类。另外,在测试 dubbo spi 时,需要在 robot 接口上标注 @spi 注解。
[2].cache spi 机制:
meta-inf/service/javax.cache.spi.cachingprovider=xxx
[3]spring spi 机制:
meta-inf/services/org.apache.commons.logging.logfactory=xxx
[4].springboot spi机制:
meta-inf/spring.factories/org.springframework.boot.autoconfigure.enableautoconfiguration=xxx
在springboot的自动装配过程中,最终会加载meta-inf/spring.factories文件,而加载的过程是由springfactoriesloader加载的。从classpath下的每个jar包中搜寻所有meta-inf/spring.factories配置文件,然后将解析properties文件,找到指定名称的配置后返回
源码:
public static final string factories_resource_location = "meta-inf/spring.factories"; // spring.factories文件的格式为:key=value1,value2,value3 // 从所有的jar包中找到meta-inf/spring.factories文件 // 然后从文件中解析出key=factoryclass类名称的所有value值 public static list<string> loadfactorynames(class<?> factoryclass, classloader classloader) { string factoryclassname = factoryclass.getname(); // 取得资源文件的url enumeration<url> urls = (classloader != null ? classloader.getresources(factories_resource_location) : classloader.getsystemresources(factories_resource_location)); list<string> result = new arraylist<string>(); // 遍历所有的url while (urls.hasmoreelements()) { url url = urls.nextelement(); // 根据资源文件url解析properties文件,得到对应的一组@configuration类 properties properties = propertiesloaderutils.loadproperties(new urlresource(url)); string factoryclassnames = properties.getproperty(factoryclassname); // 组装数据,并返回 result.addall(arrays.aslist(stringutils.commadelimitedlisttostringarray(factoryclassnames))); } return result; }
[5].自定义序列化实现spi:meta-inf/services/xxx=接口全限定名
参考学习java spi 和dubbo spi机制源码,自己动手实现序列化工具类等
版权声明:本文为博主原创文章,遵循相关版权协议,如若转载或者分享请附上原文出处链接和链接来源。
上一篇: 系统模块划分设计的思考