最近项目中使用了Java SPI机制,利用ServiceLoader来加载并实例化类。本文对Java SPI机制进行学习并对ServiceLoader进行源码分析。参见文章Java SPI机制ServiceLoader源码分析
SPI全称Service Provider Interface,是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。
Java SPI机制 - ServiceLoader - 图1
Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制,提供了通过interface寻找implement的方法。类似于IOC的思想,将装配的控制权移到程序之外,从而实现解耦。
适应场景:调用者根据需要,使用、扩展或替换实现策略。
使用Java SPI需要符合的约定:

  1. Service provider提供Interface的具体实现后,在目录META-INF/services下的文件(以Interface全路径命名)中添加具体实现类的全路径名;
  2. 接口实现类的jar包存放在使用程序的classpath中;
  3. 使用程序使用ServiceLoader动态加载实现类(根据目录META-INF/services下的配置文件找到实现类的全限定名并调用classloader来加载实现类到JVM);
  4. 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中,文件名称为接口的全限定名,文件内容为所有实现类的全限定名。
Java SPI机制 - ServiceLoader - 图2

  • 工具类

为了代码显得整洁,实现工具类来调用ServiceLoder获取实现类。
package com.example.demo.util;

import com.example.demo.service.PayService;

import java.util.ServiceLoader;

public class ServiceObtain {

  1. **public** **void** **showAllServices(){**<br /> ServiceLoader**<**PayService**>** serviceLoader **=** ServiceLoader**.**load**(**PayService**.**class**);**
  2. **for(**PayService ele **:** serviceLoader**){**<br /> ele**.**pay**();**<br /> **}**<br /> **}**<br />**}**
  • 主程序

在主程序中调用工具类来展示ServiceLoader的结果。
@SpringBootApplication
public class DemoApplication {

  1. **public** **static** **void** **main(**String**[]** args**)** **{**
  2. SpringApplication**.**run**(**DemoApplication**.**class**,** args**);**
  3. 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);
}

  1. **public** **static** **<**S**>** ServiceLoader**<**S**>** **load(**Class**<**S**>** service**,**ClassLoader loader**)**<br /> **{**<br /> **return** **new** ServiceLoader**<>(**service**,** loader**);**<br /> **}**
  2. **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 /> **}**
  3. **public** **void** **reload()** **{**<br /> providers**.**clear**();**<br /> lookupIterator **=** **new** LazyIterator**(**service**,** loader**);**<br /> **}**<br />当我们在程序中遍历返回的ServiceLoader实例时,会使用创建的迭代器。<br />下面的hasNextService函数的fullName便为配置文件的全限定名,使用classloadergetSystemResource来获取配置文件中的实现类的全限定名。<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实例在多线程环境中不安全。