概述
最近在阅读hibernate-validator源码,接触到了java spi机制,借此了解学习java spi机制。
什么是spi?
spi全称Service Provider Interface, 是java提供的一套用来加载第三方实现或扩展的服务发现机制,利用spi可以根据实际需求,轻松的启用、扩展、或者替换对应接口的实现策略,在许多知名框架中都利用了该项机制,将服务接口与服务实现分离以达到解耦,提高程序可扩展性。
spi接口的约定
- 在META-INF/services/目录中创建以接口全限定名命名的文件,该文件内容为api具体实现类的全限定名。
- 实现类必须有一个无参构造。
- 主程序通过java.util.ServiceLoader扫描META-INF/services目录下的配置文件找到实现类的全限定名,并且将类加载到主程序中。
- 实现类所在的jar包必须放在主程序的classpath中。
加载过程
此处以hibernate-validator为例,说明spi的加载流程
定义接口
ServiceLoader也可以用于加载自定以的接口,此时需要先定义接口:
javax.validation.spi.ValidationProvider
public interface ValidationProvider<T extends Configuration<T>> {
T createSpecializedConfiguration(BootstrapState state);
Configuration<?> createGenericConfiguration(BootstrapState state);
ValidatorFactory buildValidatorFactory(ConfigurationState configurationState);
}
配置实现类
META-INF/
|—services/
`—javax.validation.spi.ValidationProvider
javax.validation.spi.ValidationProvider中的内容为:
org.hibernate.validator.HibernateValidator
加载实现类
ServiceLoader中采用懒加载机制,动态加载需要的配置文件以及配置类
ServiceLoader<ValidationProvider> loader = ServiceLoader.load( ValidationProvider.class, classloader );
Iterator<ValidationProvider> providerIterator = loader.iterator();
// ServiceLoader采用的懒加载机制,执行hasNext()时,才会触发加载配置文件。
while ( providerIterator.hasNext() ){
// 若实现类未加载,则此时加载实现类的class文件并实例化对象
providerIterator.next();
}
ServiceLoader原理
ServiceLoader中的成员变量
// 扫描目录前缀
private static final String PREFIX = "META-INF/services/";
// 被加载的类或接口
private final Class<S> service;
// 用于定位、加载和实例化实现类的类加载
private final ClassLoader loader;
// 访问控制器
private final AccessControlContext acc;
// 缓存已经实例化的实现类
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 懒加载迭代器
private LazyIterator lookupIterator;
ServiceLoader中,主要的源码逻辑为:
1. 应用程序调用ServiceLoader.load方法,初始化类加载器、访问控制器、实现类缓存以及懒加载迭代器
2. 应用程序通过迭代器接口获取实现类:
ServiceLoader中存在两个迭代器,调用ServiceLoader的迭代器接口返回的是普通迭代器,迭代器的hasNext()及next()方法中,均先从缓存中查询实现类,当缓存查询完毕之后,则调用懒加载迭代器读取配置文件及实例化实现类:
普通迭代器的hasNext()方法及next()方法
public boolean hasNext() {
if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {
if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
懒加载迭代器中,获取配置文件的实现为:
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);
}
3. 加载类对象,通过反射方法Class.forName()加载类对象,并用instance()方法实例化。
4. 最后,将加入实例化后的类缓存到providers缓存中。
总结
- 使用Java spi机制的优势是实现解耦,将第三方实现与调用者业务代码分离,可以轻松实现业务模块之间的扩展或替换。
- 实现扩展对于原来的代码没有侵入性。
- 使用时不必关心实现类的路径。
最后
以上就是高兴酸奶为你收集整理的JAVA SPI机制什么是spi?spi接口的约定加载过程ServiceLoader原理总结的全部内容,希望文章能够帮你解决JAVA SPI机制什么是spi?spi接口的约定加载过程ServiceLoader原理总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复