最近项目中使用了Java SPI机制,利用ServiceLoader来加载并实例化类。本文对Java SPI机制进行学习并对ServiceLoader进行源码分析。参见文章Java SPI机制和 ServiceLoader源码分析。
SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。
Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制,提供了通过interface寻找implement的方法。类似于IOC的思想,将装配的控制权移到程序之外,从而实现解耦。
适应场景:调用者根据需要,使用、扩展或替换实现策略。
使用Java SPI需要符合的约定:Service provider提供Interface的具体实现后,在目录META-INF/services下的文件(以Interface全路径命名)中添加具体实现类的全路径名;
接口实现类的jar包存放在使用程序的classpath中;
使用程序使用ServiceLoader动态加载实现类(根据目录META-INF/services下的配置文件找到实现类的全限定名并调用classloader来加载实现类到JVM);
SPI的实现类必须具有无参数的构造方法。
Java SPI使用示例定义接口
package com.example.demo.service;
public interface PayService {
void pay();
}提供实现类
我们这里提供了接口的2个实现类。
AlipayService
package com.example.demo.service.impl;
import com.example.demo.service.PayService;
public class AlipayService implements PayService {
@Override
public void pay() {
System.out.println("Alipay");
}
}
WeixinpayService
package com.example.demo.service.impl;
import com.example.demo.service.PayService;
public class WeixinpayService implements PayService {
@Override
public void pay() {
System.out.println("Weixin Pay");
}
}配置文件
配置文件存放在目录META-INF/services中,文件名称为接口的全限定名,文件内容为所有实现类的全限定名。工具类
为了代码显得整洁,实现工具类来调用ServiceLoder获取实现类。
package com.example.demo.util;
import com.example.demo.service.PayService;
import java.util.ServiceLoader;
public class ServiceObtain {
public void showAllServices(){
ServiceLoader serviceLoader = ServiceLoader.load(PayService.class);
for(PayService ele : serviceLoader){
ele.pay();
}
}
}主程序
在主程序中调用工具类来展示ServiceLoader的结果。
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
ServiceObtain serviceObtain = new ServiceObtain();
serviceObtain.showAllServices();
}
}结果
这里,我们省略了无用的log。通过log,我们可知,ServiceLoader获取配置文件中列出的实现类并使用classloader加载到JVM中。
Started DemoApplication in 1.952 seconds (JVM running for 2.441)
Alipay
Weixin Pay
ServiceLoder源码分析
通过示例,我们已经知道了ServiceLoader的作用。下面,我们通过分析源码来看看ServiceLoader是怎么实现的。
根据ServiceLoader的入口load()函数,我们知道ServiceLoader创建LazyIterator,而且ServiceLoader类也实现了Interator。
public static ServiceLoader load(Class service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static ServiceLoader load(Class service,ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
private ServiceLoader(Class 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);
}
当我们在程序中遍历返回的ServiceLoader实例时,会使用创建的迭代器。
下面的hasNextService函数的fullName便为配置文件的全限定名,使用classloader的getSystemResource来获取配置文件中的实现类的全限定名。
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;
}
下面的nextService函数对每个实现类的全限定名使用Class.forName来获得相应的class,调用Class.newInstance来生成类的实例,最后将创建的实例和类名放入providers中。
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 }
总结
使用Java SPI机制能够在service provider与service user之间进行解耦,同时,只有在使用的时候才主动加载实现类并缓存加载的实现类,但是会加载配置文件中所有的实现类,尽管有些实现类不使用。获取指定实现类也只能通过Iterator来获取,不能通过类似Map方式直接获取。而且,ServiceLoader实例在多线程环境中不安全。