【概念简析】浅谈Java SPI机制的理解及应用
2022/2/10 22:16:15
本文主要是介绍【概念简析】浅谈Java SPI机制的理解及应用,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
Java SPI(Service Provider Interface),是JDK提供的一套用来被第三方实现或者扩展的接口,通过java.util.ServiceLoader类加载META-INF/services/中的配置进行服务发现,可以用来启用框架扩展和替换组件。主要好处在于解耦,可拔插,面向接口编程,本质是基于接口的编程+策略模式+约定配置文件组合实现的动态加载机制。
这种思想被广泛的应用到各种框架及其实现中,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和Oracle都有不同的实现提供,而Java的SPI机制可以为某个接口寻找服务实现。再比如Spring中也有一种类似与Java SPI的扩展加载机制,在META-INF/spring.factories文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。这种自定义的SPI机制是Spring Boot Starter实现的基础。
这里要注意SPI与API区别:
-
API大多数情况下,都是实现方制定并、实现接口,调用方仅仅调用接口,且不能选择实现, 从使用人员上来说,API 一般被应用开发人员使用;
-
SPI 是调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现, 从使用人员上来说,SPI 被框架扩展人员使用。
换句话说,API 为操作提供特定的类、方法,SPI 通过操作来符合特定的类、方法。
使用Java的SPI来模拟一个简单的实现
定义一个数据源加载接口
/** * 数据源驱动程序 * * @author starsray * @since 2022-02-10 */ public interface DatasourceDriver { void loadDriver(); }
根据规范实现接口
- MySQL实现
/** * mysql driver * * @author starsray * @since 2022-02-10 */ public class MySQLDriver implements DatasourceDriver { @Override public void loadDriver() { System.out.println("loaded mysql driver"); } }
- Oracle实现
/** * oracle driver * * @author starsray * @since 2022-02-10 */ public class OracleDriver implements DatasourceDriver { @Override public void loadDriver() { System.out.println("loaded oracle driver"); } }
测试代码
/** * TestSPI * * @author starsray * @since 2022-02-10 */ public class TestSPI { public static void main(String[] args) { ServiceLoader<DatasourceDriver> drivers = ServiceLoader.load(DatasourceDriver.class); for (DatasourceDriver d : drivers) { d.loadDriver(); } } }
输出
loaded mysql driver loaded oracle driver
ServiceLoader简析,查看JDK1.8的源码
主要成员变量
// 默认扫描路径 private static final String PREFIX = "META-INF/services/"; // 被加载的类或者接口 private final Class<S> service; // 用于定位、加载和实例化实现方实现的类的类加载器 private final ClassLoader loader; // ServiceLoader被创建后的上下文对象 private final AccessControlContext acc; // 按照实例化的顺序缓存已经实例化的类 private LinkedHashMap<String,S> providers = new LinkedHashMap<>(); // The current lazy-lookup iterator 懒加载器 private LazyIterator lookupIterator;
当调用静态load方法时,会创建一个新的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(); }
reload方法中,清理服务提供缓存,创建懒加载迭代器。此时并不会加载服务对象。
public void reload() { providers.clear(); lookupIterator = new LazyIterator(service, loader); }
由于LazyIterator实现了Iterator接口,当通过迭代器循环时,扫描获取所有的服务类并装载到缓存中,供下次使用。
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的实现ServiceLoader,借助于这种思想,每种框架的实现方式大同小异,比如Spring Boot Starter还引入了注解扫描,注册,按需加载的实现形式。
完整代码示例:https://gitee.com/starsray/test/tree/master/spi-test
这篇关于【概念简析】浅谈Java SPI机制的理解及应用的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解
- 2024-11-23Java对接阿里云智能语音服务入门教程
- 2024-11-23JAVA对接阿里云智能语音服务入门教程
- 2024-11-23Java副业入门:初学者的简单教程
- 2024-11-23JAVA副业入门:初学者的实战指南