实现一个简单的Java SPI框架

1. 什么是Java SPI 技术

SPI的全名是Service Provider Interface,可能我们很多开发人员对这项技术不是特别的熟悉,因为这项技术最早是Java提供给厂商和插件开发者的技术支持,java.util.ServiceLoader的文档里有比较详细。简单的总结一下 java spi 的思想:在我们的系统中抽象出了很多的模块,往往有很多不同的实现方案,例如:日志模块、通讯模块、序列化模块等。在面向对象的编程思想中,我们推荐模块之间是基于接口进行编程的,模块之间不以实现类进行硬编码。一旦代码里有了实现类的硬编码,这就违反了插拔原则,如果要替换成另外一种实现,代码就要做非常大的改动。 Java spi就是提供这样的一个机制:为某个接口寻找服务实现的机制。有些类似于IOC的思想,将装配控制权移到应用之外。在模块化设计中这个机制尤其重要,java spi的具体约定为:当服务的提供者,提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。 基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader。

2. JDK 中的SPI技术

jdk 原生支持 spi 技术,我们先写一个小的 demo,来看一下原生 spi 是怎么一个样子。

  • 先定义一个接口:
package io.lizardframework.jdk;

public interface Iface {
	void sayHello(String name);
}

  • 定义两个接口实现类
package io.lizardframework.jdk;
public class RedFace implements Iface {
	@Override
	public void sayHello(String name) {
		System.out.println("red face say hello: " + name);
	}
}
package io.lizardframework.jdk;
public class BlueFace implements Iface {
	@Override
	public void sayHello(String name) {
		System.out.println("blue face say hello: " + name);
	}
}
  • 编写一个 main 函数
package io.lizardframework.jdk;

import java.util.Iterator;
import java.util.ServiceLoader;

public class MainFunc {

	public static void main(String[] args) {
		ServiceLoader<Iface> ifaceServiceLoader = ServiceLoader.load(Iface.class);
		Iterator<Iface>      iterator           = ifaceServiceLoader.iterator();
		while (iterator.hasNext()) {
			Iface iface = iterator.next();
			iface.sayHello("sage");
		}
	}

}
  • 我们的工程都是基于 maven 进行构建的,所以在 resources 路径下建立META-INF文件夹,文件名为接口名称,内容为接口的实现类路径:
    spi 描述文件路径

  • 新建的io.lizardframework.jdk.Iface描述文件的内容如下:
    spi 描述文件内容

  • 运行Main方法后输出结果如下:
    main 方法运行结果

2.1 核心源码分析

我们从刚刚编写的 main 方法入手,进入到ServiceLoader类,观察LazyIterator类中的hasNextService方法,即可看到如下的代码:

 private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                    // PREFIX即读取描述文件的路径:"META-INF/services/"
                    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;
        }

2.2 JDK SPI的缺点

从上面的实例代码中,我们可以看到 jdk 原生的 spi 实现虽然使用方便,但是无法提前预知要创建的实例化对象,必须全部遍历创建对象才可以。这样会导致无用的实现也会被实例化,造成资源的浪费和冲突。同时无法做到单例初始化,需要自己实现缓存才可以,编码不灵活。

3. Lizard-SPI 介绍

Lizard-SPI 是一个基于 Java Spi 和 Dubbo Spi 思想基础上的 SPI 支持框架,让使用方快速灵活的扩展应用程序。

  • 支持 单例扩展 和 prototype 扩展
  • 扩展描述文件支持 key value 模式的多实现配置
  • 不与任何第三方框架强耦合

3.1 整体源码结构

io.lizardframework.spi
|
| -- common				  # 工具包
| -- ExtensionClass	      # 扩展接口实现类包装
| -- ExtensionFactory	  # 扩展点加载器工厂
| -- ExtensionLoader      # 扩展点加载器
| -- Extensions           # 扩展SPI实现注解
| -- SPI                  # 扩展点接口标示

ExtensionLoader不直接使用构造方法创建,而是通过 ExtensionFactory 工厂的 getExtensionLoader 方法获取到对应类型的扩展点加载器。ExtensionLoader有两个比较重要的方法:

  • public T getExtension(String name, Class[] argTypes, Object[] args):加载指定名称和构造函数的实现类
  • public T getExtension():加载名称为 default 且构造函数无参数的默认实现类

3.2 运行流程分析

我们首先来看下ExtensionFactory获取ExtensionLoader的过程:

package io.lizardframework.spi;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ExtensionFactory {
	private ExtensionFactory() {
	}

	private static final Map<Class, ExtensionLoader> EXTENSION_LOADER_MAP = new ConcurrentHashMap<>();

	/**
	 * 获取指定接口的扩展点加载器
	 *
	 * @param clazz
	 * @param <T>
	 * @return
	 */
	public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> clazz) {
		ExtensionLoader loader = EXTENSION_LOADER_MAP.get(clazz);
		if (loader == null) {
			synchronized (ExtensionFactory.class) {
				loader = EXTENSION_LOADER_MAP.get(clazz);
				if (loader == null) {
					EXTENSION_LOADER_MAP.putIfAbsent(clazz, new ExtensionLoader(clazz));
					loader = EXTENSION_LOADER_MAP.get(clazz);
				}
			}
		}
		return loader;
	}
}

我们可以看到,每一个扩展点接口都有一个对应的ExtensionLoader实例。再来看下ExtensionLoader类实例化的流程:

public ExtensionLoader(Class<T> type) {
		this.type = type;
		// 判断是否有SPI注解
		this.isExtensionAnnontation(this.type);
		// 装载该接口的SPI扩展实现和 name 的缓存
		this.loadExtensionClasses();
	}

这里的核心流程是 loadExtensionClasses() 方法,规定了扩展点描述文件的装载路径和装配方式。默认读取的路径是 META-INF/lizard/services/ 和 META-INF/lizard/internal/

	/**
	 * 装载该接口的SPI扩展实现和 name 的缓存
	 * 使用synchronized同步,避免多次装载
	 * @return
	 */
	private synchronized void loadExtensionClasses() {
		this.loadFiles(SERVICR_DIRECTORY);
		this.loadFiles(LIZARD_INTERNAL_DIRECTORY);
	}

	/**
	 * 装载SPI描述文件
	 *
	 * @param dir
	 */
	private void loadFiles(String dir) {
		// 获取类型扩展点路径
		String fileName = dir + this.type.getName();

		try {
			// 获取ClassLoader,并装载配置文件
			ClassLoader      classLoader = ClassLoaderUtils.getCurrentClassLoader();
			Enumeration<URL> urls        = classLoader != null ? classLoader.getResources(fileName) : ClassLoader.getSystemResources(fileName);

			if (urls != null) {
				while (urls.hasMoreElements()) {
					URL    url  = urls.nextElement();
					String line = null;
					try (BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream(), DEFAULT_CHARSET))) {
						while ((line = reader.readLine()) != null) {
							line = line.trim();
							// # 开头为注释,直接忽略
							if (line.startsWith("#")) continue;

							// name=实现接口
							int    equalIndex = line.indexOf("=");
							String name       = null;
							String clazzName  = null;
							// 获取 name 和实现类名
							if (equalIndex > 0) {
								name = line.substring(0, equalIndex).trim();
								clazzName = line.substring(equalIndex + 1).trim();
							}
							// 如果实现类不为空
							if (clazzName != null && clazzName.length() > 0) {
								// 第二个参数 true 初始化,执行 static 块
								Class<?> clazz = Class.forName(clazzName, true, classLoader);

								// 如果当前配置的实现类不是 type 的实现,抛出异常
								if (!this.type.isAssignableFrom(clazz)) {
									throw new IllegalStateException("Error when load extension class(interface: " +
											this.type + ", class line: " + clazz.getName() + "), class " + clazz.getName() +
											" is not subtype of interface");
								}

								// 判断实现类是否标有Extensions注解
								Extensions extensions = clazz.getAnnotation(Extensions.class);
								if (extensions == null) {
									throw new IllegalStateException("Error when load extension class(interface: " +
											this.type + ", class line: " + clazz.getName() + "), class " + clazz.getName() +
											" without @" + Extensions.class + " Annontation");
								}

								// 通过 name 获取是否已经装载过同名的扩展配置类
								ExtensionClass<T> alreadyClazz = CACHE_EXTENSION_CLAZZ.get(name);
								if (alreadyClazz == null) {
									CACHE_EXTENSION_CLAZZ.put(name, new ExtensionClass(clazz, name, extensions));
								} else {
									throw new IllegalStateException("Duplicate extension " + this.type.getName() + " name: " + name
											+ " on: " + alreadyClazz.getClazz() + " and: " + clazz.getName() + " in: " + fileName);
								}
							}
						}
					} catch (Exception cause) {
						// 将异常写入 map,当获取 clazz 为空时,就将异常输出
						exceptions.put(line, cause);
					}
				}
			}
		} catch (IOException e) {
			//todo logger
		}
	}

扩展点的每一个实现类都被包装为ExtensionClass对象,getInstance方法通过@Extensions注解,判断实例化对象是否为单例:

public class ExtensionClass<T> {
	/**
	 * 实现类的Extensions注解
	 */
	private          Extensions         extensions;
	
	/**
	 * 获取实例对象
	 *
	 * @param argTypes
	 * @param args
	 * @return
	 */
	public T getInstance(Class[] argTypes, Object[] args) {
		if (clazz != null) {
			if (extensions.singleton()) {
				// 创建单例对象
				if (instance == null) {
					synchronized (this) {
						if (instance == null) {
							instance = ClassUtils.newInstanceWithArgs(clazz, argTypes, args);
						}
					}
				}
				return instance;
			} else {
				// 直接创建对象返回
				return ClassUtils.newInstanceWithArgs(clazz, argTypes, args);
			}
		}
		throw new IllegalStateException("class of ExtensionClass is null");
	}
}

最后,调用ExtensionLoader的getExtension方法,获取对应接口的扩展实现。如果调用无参方法,默认的 name 为 default,同时调用无参构造函数创建单例实现类。这里我们就不再具体赘述。

源码地址

Lizard-SPI的源码和单元测试实例已经全部上传到 github 中,欢迎大家试用并提出改正意见,谢谢!
github:https://github.com/lizard-framework/lizard-spi

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值