serviceloader java_Java SPI机制 - ServiceLoader

最近项目中使用了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实例在多线程环境中不安全。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值