SPI的全称是Service Provider Interface,它是为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。 JAVA SPI就是提供这样的一个机制,为某个接口寻找服务实现的机制。
我们举一个小例子加以说明
首先我有一个接口IHello.java
public interface IHello {
void sayHello();
}
有两个接口的实现类HelloImpl1,HelloImpl2
public class HelloImpl1 implements IHello {
@Override
public void sayHello() {
System.out.println("我是Impl1");
}
}
public class HelloImpl2 implements IHello {
@Override
public void sayHello() {
System.out.println("我是Impl2");
}
}
在MATE-INF下面的services文件夹中有一个以接口的全限定名命名的文件,里面的内容就是实现类的全限定名。
使用时使用帮助类ServiceLoader,下面是我们具体的使用方法
public static void main(String[] args){
ServiceLoader<IHello> s = ServiceLoader.load(IHello.class);
Iterator<IHello> iHelloIterator = s.iterator();
while (iHelloIterator.hasNext()) {
IHello iHello = iHelloIterator.next();
iHello.sayHello();
}
}
我们大体解释一下,搜索IHello类型的实例,之后遍历执行,我们并没有指明是具体的哪个IHello的实现类进行操作,那么是谁指定的呢?就是META-INF/services 下面的那个文件内容决定的,如果内容是:
com.test.demo.impl.HelloImpl2
那么就由Impl2执行,如果是
com.test.demo.impl.HelloImpl1
com.test.demo.impl.HelloImpl2
就是两个都执行。在我们不需要改动代码的情况下可以动态的切换执行的实现类。
ServiceLoader是怎样执行的呢?
首先创建这个类型的ServiceLoader
public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
当需要的属性都准备完毕,开始reload
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();
}
关注这个LazyIterator的操作,查询实现类都是通过这个类进行的。
public void reload() {
providers.clear();
lookupIterator = new LazyIterator(service, loader);
}
这个方法是LazyIterator判断有没有下一个服务的方法,我们可以先看一下
这个fullName就是前缀(META-INF/services/)加上要搜索服务的name组成,所以我们的文件的路径及名称都已经确定了,只能这么写,否则搜索不到,我们再来看一下parse方法,就是解析文件中的内容放到Iterator中
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;
}
下面是获取service的过程,经过上一步,基本上都解析出来实现类的名称了,所以要通过Class.forName反射加载这个类的Class对象,然后实例化(newInstance),最后返回。
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
}
上面就是这个ServiceLoader执行的流程。这个SPI大多数开发人员可能不熟悉,因为这个是针对厂商或者插件的,现在很多软件都已采用或者扩展了这种。比如说dubbo,jdbc等,还是有必要了解一下的。
网友评论