java:解决SPI机制遇到的非典型问题-ServiceLoad.load(Class<T> service)方法失效

Java SPI 的实现原理并不复杂,它的实现基于 Java 类加载机制和反射机制。
当使用 ServiceLoader.load(Class<T> service) 方法加载服务时,会检查 META-INF/services 目录下是否存在以接口全限定名命名的文件。如果存在,则读取文件内容,获取实现该接口的类的全限定名,并通过 Class.forName() 方法加载对应的类。

以下是我的项目中基于SPI加载接口实例的代码:

	/*** SPI(Service Provider Interface)机制加载 {@link IRowMetaData}所有实例* @return 表名和 {@link RowMetaData}实例的映射对象 */private static HashMap<String, RowMetaData> loadRowMetaData() {		ServiceLoader<IRowMetaData> providers = ServiceLoader.load(IRowMetaData.class);Iterator<IRowMetaData> itor = providers.iterator();IRowMetaData instance;HashMap<String, RowMetaData> m = new HashMap<>();while(itor.hasNext()){try {instance = itor.next();				} catch (ServiceConfigurationError e) {// 实例初始化失败输出错误日志后继续循环SimpleLog.log(e.getMessage());continue;}if(instance instanceof RowMetaData){RowMetaData rowMetaData = (RowMetaData)instance;m.put(rowMetaData.tablename, rowMetaData);}}return m;}

这个代码写了几年了,在Windows,Linux,Android平台都没有任何问题。但最近在客户的设备上(Android)出了问题。
我们提供的DEMO程序在客户的设备上也能正常执行,但放在客户的项目代码中,就不行。
通过跟踪发现itor.hasNext()返回false,就导致没有执行循环,应该是没有找到META-INF/services 目录下接口IRowMetaData的全限定名命名的文件。

进一步跟踪,找到itor.hasNext()【java.util.ServiceLoader.LayIterator.hasNext()】最终调用的实现代码

        private boolean hasNextService() {if (nextName != null) {return true;}if (configs == null) {try {String fullName = PREFIX + service.getName();if (loader == null)configs = ClassLoader.getSystemResources(fullName);elseconfigs = 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;}

可以看到最终是通过调用ClassLoader.getResources(String)来读取META-INF/services 目录下接口类的全限定名命名的文件,根据ClassLoader.getResources(String)的方法说明,如果没有找到对应的文件,则返回空的对象。

所以itor.hasNext()返回false的直接原因应该就是没找到META-INF/services 目录下接口类的全限定名命名的文件。但是为什么会这样,没搞明白。
进一步看ServiceLoader的代码,才知道,ServiceLoader.load其实有两个重载方法,如下:

    public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader){return new ServiceLoader<>(service, loader);}public static <S> ServiceLoader<S> load(Class<S> service) {ClassLoader cl = Thread.currentThread().getContextClassLoader();return ServiceLoader.load(service, cl);}

常用的ServiceLoader.load(Class<T> service)方法的实现其实是使用当前线程上下文的ClassLoader实例为参数调用了ServiceLoader.load(Class<T> service,ClassLoader loader)方法

于是我尝试改为调用ServiceLoader.load(Class<T> service,ClassLoader loader)方法,以接口类的ClassLoader实例作为loader参数,如下:

ServiceLoader<IRowMetaData> providers = ServiceLoader.load(IRowMetaData.class,IRowMetaData.class.getClassLoader());

居然可以正常执行了。

为什么会这样?太奇怪了,我无法解释,最有可能是的原因是客户的项目中的使用某种应用框架有自己的ClassLoader作为当前线程上下文的ClassLoader实例,.这个ClassLoader有BUG,找不到META-INF/services 目录下的资源文件。

不管怎样,为了安全起见,兼容两种加载方式,我将获取 实例迭代器(Iterator)的逻辑修改为如下:
即优先调用ServiceLoader.load(Class<T> service)如果返回空则调用ServiceLoader.load(Class<T> service,ClassLoader loader)方法

	/*** SPI(Service Provider Interface)机制加载 {@link IRowMetaData}所有实例* @return 表名和 {@link RowMetaData}实例的映射对象 */private static HashMap<String, RowMetaData> loadRowMetaData() {		// 优先调用ServiceLoader.load(Class<T> service)// 如果返回空则调用ServiceLoader.load(Class<T> service,ClassLoader loader)方法Iterator<IRowMetaData> itor = ServiceLoader.load(IRowMetaData.class).iterator();if(!itor.hasNext()){itor = ServiceLoader.load(IRowMetaData.class,IRowMetaData.class.getClassLoader()).iterator();}IRowMetaData instance;HashMap<String, RowMetaData> m = new HashMap<>();while(itor.hasNext()){try {instance = itor.next();				} catch (ServiceConfigurationError e) {// 实例初始化失败输出错误日志后继续循环SimpleLog.log(e.getMessage());continue;}if(instance instanceof RowMetaData){RowMetaData rowMetaData = (RowMetaData)instance;m.put(rowMetaData.tablename, rowMetaData);}}return m;}

这个问题解决了,但感觉是莫名奇妙,我查了很多关于第三方库及JDK的源码,关于SPI的使用方式都是调用ServiceLoader.load(Class<T> service),很少有特别强制要调用ServiceLoader.load(Class<T> service,ClassLoader loader)方法,大家都是这么用的,到我这里却出了毛病,很难向客户证明这不是我们的问题。
好在可以通过修改我们代码,增加容错性逻辑解决了问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/223323.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uniCloud(一) 新建项目、初始化服务空间、云对象访问测试

一、新建一个带有unicloud 二、创建一个服务空间 1. 右键uniCloud&#xff0c;关联云服务空间 我当前没有服务空间&#xff0c;需要新建一个服务空间&#xff0c;之后将其关联。初始化服务空间需要的时间有点长 服务空间初始化成功后&#xff0c;刷新HBuilder&#xff0c;勾选…

Linux系统下CPU性能问题分析案例

&#xff08;上&#xff09; 本文涉及案例来自于学习极客时间专栏《Linux性能优化实战》精心整理而来&#xff0c;案例总结不到位的请各位多多指正。 某个应用的CPU使用率居然达到100%&#xff0c;我该怎么办&#xff1f; 分析过程 使用观察系统CPU使用情况&#xff08;并按下…

03. 医院设置_后端

1、Swagger2 测试工具 编写和维护接口文档是每个程序员的职责&#xff0c;根据Swagger2可以快速帮助我们编写最新的API接口文档&#xff0c;再也不用担心开会前仍忙于整理各种资料了&#xff0c;间接提升了团队开发的沟通效率。 swagger通过注解表明该接口会生成文档&#xf…

docker版zerotier-planet服务端搭建

1:ZeroTier 介绍2:为什么要自建PLANET 服务器3:开始安装 3.1:准备条件 3.1.1 安装git3.1.2 安装docker3.1.3 启动docker3.2:下载项目源码3.3:执行安装脚本3.4 下载 planet 文件3.5 新建网络 3.5.1 创建网络4.客户端配置 4.1 Windows 配置 4.2 加入网络4.2 Linux 客户端4.…

vuepress-----25、右侧目录

# 25、vuepress 右侧目录 https://github.com/xuek9900/vuepress-plugin-right-anchor vuepress-plugin-right-anchor English &#xff5c;中文 在用 Vuepress 2.x 编写的文档页面右侧添加 锚点导航栏 # 版本 2.x.x -> Vuepress 2.x -> npm next -> master 分支0…

PS扣印章

1 印章区域图片 2 3 吸取印章上的颜色&#xff0c;调节容差&#xff0c;尽量小一点&#xff0c;过大会将背景也进来 4 CtrlJ 把选区复制出来&#xff0c;这个印章图层比较淡&#xff0c;可以通过多复制几个叠加或通过叠加模式来调节。 5 对几个图层选中后CtrlE合并图层 6 选…

IT圈茶余饭后的“鄙视链”

哈哈&#xff0c;IT圈的鄙视链&#xff0c;简直就是一出情感大戏&#xff01;这个圈子里的人们总是忍不住要互相比较&#xff0c;互相鄙视&#xff0c;仿佛这是一场刺激的游戏&#xff0c;每个人都想要站在鄙视链的最顶端&#xff0c;成为那个最牛逼的存在。 首先&#xff0c;…

深度学习第5天:GAN生成对抗网络

☁️主页 Nowl &#x1f525;专栏 《深度学习》 &#x1f4d1;君子坐而论道&#xff0c;少年起而行之 ​​ 文章目录 一、GAN1.基本思想2.用途3.模型架构 二、具体任务与代码1.任务介绍2.导入库函数3.生成器与判别器4.预处理5.模型训练6.图片生成7.不同训练轮次的结果对比 一…

CSS特效030:日蚀动画

CSS常用示例100专栏目录 本专栏记录的是经常使用的CSS示例与技巧&#xff0c;主要包含CSS布局&#xff0c;CSS特效&#xff0c;CSS花边信息三部分内容。其中CSS布局主要是列出一些常用的CSS布局信息点&#xff0c;CSS特效主要是一些动画示例&#xff0c;CSS花边是描述了一些CSS…

高级C#技术(二)

前言 本章为高级C#技术的第二节也是最后一节。前一节在下面这个链接 高级C#技术https://blog.csdn.net/qq_71897293/article/details/134930989?spm1001.2014.3001.5501 匿名类型 匿名类型如其名&#xff0c;匿名的没有指定变量的具体类型。 举个例子&#xff1a; 1 创建…

SpringBoot运维中的高级配置

&#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;程序员老茶 &#x1f64a; ps:点赞&#x1f44d;是免费的&#xff0c;却可以让写博客的作者开心好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#xff0c;…

k8s1.24+prometheus+grafana容器监控与告警

快速部署k8s-1.24 基础配置[三台centos] 1.关闭防火墙与selinux systemctl stop firewalld systemctl disable firewalld sed -i ‘s/enforcing/disabled/’ /etc/selinux/config setenforce 0 2.添加host记录 cat >>/etc/hosts <<EOF 192.168.180.210 k8s-master…

2023年【烟花爆竹经营单位主要负责人】免费试题及烟花爆竹经营单位主要负责人模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 烟花爆竹经营单位主要负责人免费试题参考答案及烟花爆竹经营单位主要负责人考试试题解析是安全生产模拟考试一点通题库老师及烟花爆竹经营单位主要负责人操作证已考过的学员汇总&#xff0c;相对有效帮助烟花爆竹经营…

前端复制文本、下载图片

复制文本 // 复制 copy(data) {var oInput document.createElement("input");oInput.value data;document.body.appendChild(oInput);oInput.select();document.execCommand("Copy");oInput.style.display "none"; }input不能识别\n&#xf…

vscode报错:建立连接:XHR failed

文章目录 问题解决方案 问题 Windows端ssh远程连接Linux端&#xff0c;Windows端vscode报错&#xff1a;“…XHR failed.” 解决方案 参考&#xff1a;解决 Windows 端 VS Code “无法与 “…“ 建立连接&#xff1a;XHR failed.” 问题 亲测有效。 总结&#xff1a; linux…

TreeSelect 树型选择控件 编辑回显时所选的值与展开后的数据不对应 解决方案

一、业务场景&#xff1a; 最近在使用Vue框架和antd-vue组件库的时候&#xff0c;发现在做编辑回显时** TreeSelect 树型选择控件** 组件的选中的值能拿到&#xff0c;但是在下拉列表的回显位置有偏差。为了大家后面遇到和我一样的问题&#xff0c;给大家分享一下 二、bug信息…

基于ssm毕业生就业管理平台论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本毕业生就业管理平台就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信…

Mesh里面的MFB是什么文件,工程重置了能否还原?

答&#xff1a;模方工程重置了以后&#xff0c;如果有备份可以还原。 模方是一款针对实景三维模型的冗余碎片、水面残缺、道路不平、标牌破损、纹理拉伸模糊等共性问题研发的实景三维模型修复编辑软件。模方4.1新增自动单体化建模功能&#xff0c;支持一键自动提取房屋结构&am…

JVM垃圾回收算法

自动化的管理内存资源&#xff0c;垃圾回收机制必须要有一套算法来进行计算&#xff0c;哪些是有效的对象&#xff0c;哪些是无效的对象&#xff0c;对于无效的对象就要进行回收处理。 常见的垃圾回收算法有&#xff1a;引用计数法、标记清除法、标记压缩法、复制算法、分代算…

如何消除视频中的背景噪音

如果你在繁忙的街道上、刮风的日子、或在其他有嘈杂声音的周围拍摄视频&#xff0c;则会产生令人烦恼的噪音。幸运的是&#xff0c;从视频中消除背景噪音并不是一件困难的事情&#xff0c;因为有许多可靠的降噪软件可以提供帮助。本文就收集了3种最佳方法&#xff0c;可帮助你轻…