最近项目中使用了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(){**<br /> ServiceLoader**<**PayService**>** serviceLoader **=** ServiceLoader**.**load**(**PayService**.**class**);**
**for(**PayService ele **:** serviceLoader**){**<br /> ele**.**pay**();**<br /> **}**<br /> **}**<br />**}**
- 主程序
在主程序中调用工具类来展示ServiceLoader的结果。
@SpringBootApplication
public class DemoApplication {
**public** **static** **void** **main(**String**[]** args**)** **{**
SpringApplication**.**run**(**DemoApplication**.**class**,** args**);**
ServiceObtain serviceObtain **=** **new** ServiceObtain**();**<br /> serviceObtain**.**showAllServices**();**<br /> **}**
}
- 结果
这里,我们省略了无用的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 <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
**public** **static** **<**S**>** ServiceLoader**<**S**>** **load(**Class**<**S**>** service**,**ClassLoader loader**)**<br /> **{**<br /> **return** **new** ServiceLoader**<>(**service**,** loader**);**<br /> **}**
**private** **ServiceLoader(**Class**<**S**>** svc**,** ClassLoader cl**)** **{**<br /> service **=** Objects**.**requireNonNull**(**svc**,** "Service interface cannot be null"**);**<br /> loader **=** **(**cl **==** **null)** **?** ClassLoader**.**getSystemClassLoader**()** **:** cl**;**<br /> acc **=** **(**System**.**getSecurityManager**()** **!=** **null)** **?** AccessController**.**getContext**()** **:** **null;**<br /> reload**();**<br /> **}**
**public** **void** **reload()** **{**<br /> providers**.**clear**();**<br /> lookupIterator **=** **new** LazyIterator**(**service**,** loader**);**<br /> **}**<br />当我们在程序中遍历返回的ServiceLoader实例时,会使用创建的迭代器。<br />下面的hasNextService函数的fullName便为配置文件的全限定名,使用classloader的getSystemResource来获取配置文件中的实现类的全限定名。<br /> **private** **boolean** **hasNextService()** **{**<br /> **if** **(**nextName **!=** **null)** **{**<br /> **return** **true;**<br /> **}**<br /> **if** **(**configs **==** **null)** **{**<br /> **try** **{**<br /> String fullName **=** PREFIX **+** service**.**getName**();**<br /> **if** **(**loader **==** **null)**<br /> configs **=** ClassLoader**.**getSystemResources**(**fullName**);**<br /> **else**<br /> configs **=** loader**.**getResources**(**fullName**);**<br /> **}** **catch** **(**IOException x**)** **{**<br /> fail**(**service**,** "Error locating configuration files"**,** x**);**<br /> **}**<br /> **}**<br /> **while** **((**pending **==** **null)** **||** **!**pending**.**hasNext**())** **{**<br /> **if** **(!**configs**.**hasMoreElements**())** **{**<br /> **return** **false;**<br /> **}**<br /> pending **=** parse**(**service**,** configs**.**nextElement**());**<br /> **}**<br /> nextName **=** pending**.**next**();**<br /> **return** **true;**<br /> **}**<br />下面的nextService函数对每个实现类的全限定名使用Class.forName来获得相应的class,调用Class.newInstance来生成类的实例,最后将创建的实例和类名放入providers中。<br /> **private** S **nextService()** **{**<br /> **if** **(!**hasNextService**())**<br /> **throw** **new** NoSuchElementException**();**<br /> String cn **=** nextName**;**<br /> nextName **=** **null;**<br /> Class**<?>** c **=** **null;**<br /> **try** **{**<br /> c **=** Class**.**forName**(**cn**,** **false,** loader**);**<br /> **}** **catch** **(**ClassNotFoundException x**)** **{**<br /> fail**(**service**,**<br /> "Provider " **+** cn **+** " not found"**);**<br /> **}**<br /> **if** **(!**service**.**isAssignableFrom**(**c**))** **{**<br /> fail**(**service**,**<br /> "Provider " **+** cn **+** " not a subtype"**);**<br /> **}**<br /> **try** **{**<br /> S p **=** service**.**cast**(**c**.**newInstance**());**<br /> providers**.**put**(**cn**,** p**);**<br /> **return** p**;**<br /> **}** **catch** **(**Throwable x**)** **{**<br /> fail**(**service**,**<br /> "Provider " **+** cn **+** " could not be instantiated"**,**<br /> x**);**<br /> **}**<br /> **throw** **new** Error**();** _// This cannot happen<br />_ **}**
总结
使用Java SPI机制能够在service provider与service user之间进行解耦,同时,只有在使用的时候才主动加载实现类并缓存加载的实现类,但是会加载配置文件中所有的实现类,尽管有些实现类不使用。获取指定实现类也只能通过Iterator来获取,不能通过类似Map方式直接获取。而且,ServiceLoader实例在多线程环境中不安全。