【Java】深入理解Java中的spi机制
深入理解java中的spi机制
spi
全名为service provider interface
是jdk内置的一种服务提供发现机制,是java提供的一套用来被第三方实现或者扩展的api,它可以用来启用框架扩展和替换组件。
java spi
= 基于接口的编程+策略模式+配置文件 的动态加载机制
java spi的具体约定如下:
当服务的提供者,提供了服务接口的一种实现之后,在jar
包的meta-inf/services/
目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。
而当外部程序装配这个模块的时候,就能通过该jar
包meta-inf/services/
里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
根据spi的规范我们的服务实现类必须有一个无参构造方法
。
为什么一定要在classes
中的meta-inf/services
下呢?
jdk提供服务实现查找的一个工具类:java.util.serviceloader
在这个类里面已经写死
// 默认会去这里寻找相关信息 private static final string prefix = "meta-inf/services/";
常见的使用场景:
-
jdbc
加载不同类型的数据库驱动 - 日志门面接口实现类加载,
slf4j
加载不同提供商的日志实现类 -
spring
中大量使用了spi
,- 对
servlet3.0
规范 - 对
servletcontainerinitializer
的实现 - 自动类型转换
type conversion spi(converter spi、formatter spi)
等
- 对
-
dubbo
里面有很多个组件,每个组件在框架中都是以接口的形成抽象出来!具体的实现又分很多种,在程序执行时根据用户的配置来按需取接口的实现
简单的spi实例
整体包结构如下
└─main ├─java │ └─com │ └─xinchen │ └─spi │ └─app.java │ └─iservice.java │ └─serviceimpla.java │ └─serviceimplb.java └─resources └─meta-inf └─services └─com.xinchen.spi.iservice
spi接口
public interface iservice { void say(string word); }
具体实现类
public class serviceimpla implements iservice { @override public void say(string word) { system.out.println(this.getclass().tostring() + " say: " + word); } } public class serviceimplb implements iservice { @override public void say(string word) { system.out.println(this.getclass().tostring() + " say: " + word); } }
/resource/meta-inf/services/com.xinchen.spi.iservice
com.xinchen.spi.serviceimpla com.xinchen.spi.serviceimplb
client类
public class app { static serviceloader<iservice> services = serviceloader.load(iservice.class); public static void main(string[] args) { for (iservice service:services){ service.say("hello world!"); } } } // 结果: // class com.xinchen.spi.serviceimpla say: hello world! // class com.xinchen.spi.serviceimplb say: hello world!
源码解析
java.util.serviceloader
中的fied区域
// 加载具体实现类信息的前缀 private static final string prefix = "meta-inf/services/"; // 需要加载的接口 // the class or interface representing the service being loaded private final class<s> service; // 用于加载的类加载器 // the class loader used to locate, load, and instantiate providers private final classloader loader; // 创建serviceloader时采用的访问控制上下文 // the access control context taken when the serviceloader is created private final accesscontrolcontext acc; // 用于缓存已经加载的接口实现类,其中key为实现类的完整类名 // cached providers, in instantiation order private linkedhashmap<string,s> providers = new linkedhashmap<>(); // 用于延迟加载接口的实现类 // the current lazy-lookup iterator private lazyiterator lookupiterator;
从serviceloader.load(iservice.class)
进入源码中
public static <s> serviceloader<s> load(class<s> service) { // 获取当前线程上下文的类加载器 classloader cl = thread.currentthread().getcontextclassloader(); return serviceloader.load(service, cl); }
在serviceloader.load(service, cl)
中
public static <s> serviceloader<s> load(class<s> service,classloader loader){ // 返回serviceloader的实例 return new serviceloader<>(service, loader); } private serviceloader(class<s> svc, classloader cl) { service = objects.requirenonnull(svc, "service interface cannot be null"); loader = (cl == null) ? classloader.getsystemclassloader() : cl; acc = (system.getsecuritymanager() != null) ? accesscontroller.getcontext() : null; reload(); } public void reload() { // 清空已经缓存的加载的接口实现类 providers.clear(); // 创建新的延迟加载迭代器 lookupiterator = new lazyiterator(service, loader); } private lazyiterator(class<s> service, classloader loader) { // 指定this类中的 需要加载的接口service和类加载器loader this.service = service; this.loader = loader; }
当我们通过迭代器获取对象实例的时候,首先在成员变量providers
中查找是否有缓存的实例对象
如果存在则直接返回,否则则调用lookupiterator
延迟加载迭代器进行加载
迭代器判断的代码如下
public iterator<s> iterator() { // 返回迭代器 return new iterator<s>() { // 查询缓存中是否存在实例对象 iterator<map.entry<string,s>> knownproviders = providers.entryset().iterator(); public boolean hasnext() { // 如果缓存中已经存在返回true if (knownproviders.hasnext()) return true; // 如果不存在则使用延迟加载迭代器进行判断是否存在 return lookupiterator.hasnext(); } public s next() { // 如果缓存中存在则直接返回 if (knownproviders.hasnext()) return knownproviders.next().getvalue(); // 调用延迟加载迭代器进行返回 return lookupiterator.next(); } public void remove() { throw new unsupportedoperationexception(); } }; }
lazyiterator的类加载
// 判断是否拥有下一个实例 private boolean hasnextservice() { // 如果拥有直接返回true if (nextname != null) { return true; } // 具体实现类的全名 ,enumeration<url> config 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; } // 转换config中的元素,或者具体实现类的真实包结构 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 { // 通过c.newinstance()实例化 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 }
总结
优点
使用java spi机制的优势是实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。
缺点
多个并发多线程使用serviceloader类的实例是不安全的
虽然serviceloader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。