百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

记录一次使用easypoi时与源码博弈的过程

toyiye 2024-09-02 02:19 3 浏览 0 评论

一、背景介绍

最近刚刚接手了保险一线之声平台的开发和维护工作,第一个需要修复的问题是:平台的事件导出成excel功能在经过一次上线之后突然不可用了,于是就开始了几轮痛苦的排查以及与源码博弈的过程。



二、问题描述

一线之声在事件查询菜单下支持将结果导出为Excel,程序中使用easypoi+apache-poi实现,此功能一直正常使用,直到从2024-04-12 12.04.39之后的任务全部都导出失败





三、问题定位

3.1 排查过程

看到这个问题后,第一反应是不是某次上线引起的?于是去查看了服务的上线记录,果然在当天出现问题前几分钟有过一次上线!!!



看到这里,心中猜测这个问题十有八九是这次上线导致的。这还不简单,去看看上线修改了什么内容~

看完上线内容之后,一言难尽,改动的内容跟这里八竿子打不着!

不由想起了那个困扰大部分程序员的问题:我就加了一行日志打印,怎么程序就报错了呢?

没办法,于是又尝试去查看线上日志,找到一个报错信息如下:

cn.afterturn.easypoi.exception.excel.ExcelExportException: Excel导出错误
    at cn.afterturn.easypoi.excel.export.ExcelExportService.createSheet(ExcelExportService.java:118)
    at cn.afterturn.easypoi.excel.ExcelExportUtil.exportExcel(ExcelExportUtil.java:87)
    at com.jd.jxqe.ph.service.export.impl.ExportRequireServiceImpl.writeOfflineExcel(ExportRequireServiceImpl.java:346)
    at com.jd.jxqe.ph.service.export.impl.ExportRequireServiceImpl.export(ExportRequireServiceImpl.java:216)
    at sun.reflect.GeneratedMethodAccessor1931.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:197)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at com.jd.jxqe.ph.common.MdcTaskDecorator.lambda$decorate$0(MdcTaskDecorator.java:26)
    at com.alibaba.ttl.TtlRunnable.run(TtlRunnable.java:59)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NoSuchMethodError: org.apache.poi.ss.usermodel.CellStyle.setAlignment(S)V
    at cn.afterturn.easypoi.excel.export.styler.ExcelExportStylerDefaultImpl.stringNoneStyle(ExcelExportStylerDefaultImpl.java:69)
    at cn.afterturn.easypoi.excel.export.styler.AbstractExcelExportStyler.createStyles(AbstractExcelExportStyler.java:44)
    at cn.afterturn.easypoi.excel.export.styler.ExcelExportStylerDefaultImpl.<init>(ExcelExportStylerDefaultImpl.java:31)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
    at cn.afterturn.easypoi.excel.export.ExcelExportService.insertDataToSheet(ExcelExportService.java:159)
    at cn.afterturn.easypoi.excel.export.ExcelExportService.createSheetForMap(ExcelExportService.java:145)
    at cn.afterturn.easypoi.excel.export.ExcelExportService.createSheet(ExcelExportService.java:115)
    ... 16 common frames omitted

3.2 白夜追凶

可以看到是在CellStyle源码中出现了报错,而项目中使用的easypoi和apache-poi版本分别如下:

<dependency>
	<groupId>org.apache.poi</groupId>
	<artifactId>poi</artifactId>
	<version>4.0.1</version>
</dependency>
<dependency>
	<groupId>org.apache.poi</groupId>
	<artifactId>poi-ooxml</artifactId>
	<version>4.0.1</version>
</dependency>

<dependency>
	<groupId>cn.afterturn</groupId>
	<artifactId>easypoi-spring-boot-starter</artifactId>
	<version>4.0.0</version>
	<exclusions>
		<exclusion>
			<artifactId>guava</artifactId>
			<groupId>com.google.guava</groupId>
		</exclusion>
	</exclusions>
</dependency>

同时由于pom中引入的某个domain包中依赖了easypoi-base 3.2版本,所以最终使用的easypoi-base版本被强制修改为了3.2,在此版本中cn.afterturn.easypoi.excel.export.ExcelExportService类下看到一段代码:



其中飘红报错的地方是因为在org.apache.poi.ss.usermodel.CellStyle这个接口中,没有定义相应的常量,为什么会出现这个问题呢?按理说并没有修改过依赖的apache-poi版本。

于是尝试往前追溯几个版本的org.apache.poi.ss.usermodel.CellStyle,终于在3.6这个版本中找到了确实曾经定义过这些常量,但是在后续的版本升级中已经弃用了。



那问题来了,为什么没有修改过pom文件,但是在2024-04-12程序上线后出现了这个源码报错的问题?带着这个问题继续追查的时候我发现编译平台上配置的编译命令启用了-U参数:



-U参数的作用是强制更新快照(snapshot)版本的依赖和插件。大致可以理解为,当你使用 mvn compile -U 或其他带有 -U 参数的命令时,Maven 会检查并下载最新的快照版本,而不是使用本地缓存的快照版本。我猜测可能是由于这个参数导致了之前的快照版本被覆盖了,而历史快照版本中存在过这些常量。



四、问题解决

4.1 初次升级版本

由于此服务历史代码较多,尝试降低apache-poi版本后出现了更多的兼容性问题,所以最终决定升级easypoi和apache-poi的版本,此时选择了easypoi 4.5.0和apache-poi 5.0.0版本的组合,至于为什么使用这个两个版本,可能当时看着比较顺眼吧(居然忘了看下版本是否兼容),这也为我后续陷入第二轮的源码追查埋下了伏笔。

4.2 二次入坑

使用上述的配置,在本地和预发验证后均没有问题,但是上线后发现偶现任务一直处于执行中的情况,此时已经意识到问题的不妙,果然在日志中发现了如下的报错:

2024-06-25 20:38:04.730 [/] [exportThreadPoolExecutor--6] ERROR org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler - Unexpected error occurred invoking async method 'public com.jd.jxqe.ph.common.ResponseResult com.jd.jxqe.ph.service.export.impl.ExportRequireServiceImpl.export(com.jd.jxqe.ph.model.dto.EventQueryDTO) throws java.lang.InterruptedException'.
java.lang.NoSuchMethodError: org.openxmlformats.schemas.spreadsheetml.x2006.main.CTFont.addNewFamily()Lorg/openxmlformats/schemas/spreadsheetml/x2006/main/CTFontFamily;
    at org.apache.poi.xssf.usermodel.XSSFFont.setFamily(XSSFFont.java:635)
    at org.apache.poi.xssf.usermodel.XSSFFont.setFamily(XSSFFont.java:647)
    at org.apache.poi.xssf.model.StylesTable.createDefaultFont(StylesTable.java:765)
    at org.apache.poi.xssf.model.StylesTable.initialize(StylesTable.java:716)
    at org.apache.poi.xssf.model.StylesTable.<init>(StylesTable.java:130)
    at org.apache.poi.ooxml.POIXMLFactory.newDocumentPart(POIXMLFactory.java:94)
    at org.apache.poi.ooxml.POIXMLDocumentPart.createRelationship(POIXMLDocumentPart.java:591)
    at org.apache.poi.ooxml.POIXMLDocumentPart.createRelationship(POIXMLDocumentPart.java:500)
    at org.apache.poi.xssf.usermodel.XSSFWorkbook.onWorkbookCreate(XSSFWorkbook.java:465)
    at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:255)
    at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:249)
    at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:237)
    at cn.afterturn.easypoi.excel.ExcelExportUtil.getWorkbook(ExcelExportUtil.java:124)
    at cn.afterturn.easypoi.excel.ExcelExportUtil.exportExcel(ExcelExportUtil.java:115)
    at com.jd.jxqe.ph.service.export.impl.ExportRequireServiceImpl.writeOfflineExcel(ExportRequireServiceImpl.java:348)
    at com.jd.jxqe.ph.service.export.impl.ExportRequireServiceImpl.export(ExportRequireServiceImpl.java:216)
    at sun.reflect.GeneratedMethodAccessor1333.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:197)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
    at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at com.jd.jxqe.ph.common.MdcTaskDecorator.lambda$decorate$0(MdcTaskDecorator.java:26)
    at com.alibaba.ttl.TtlRunnable.run(TtlRunnable.java:59)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

查看过maven依赖树以及源码后,注意到一个细节:org.openxmlformats.schemas.spreadsheetml.x2006.main.CTFont这个类在以下:

org.apache.poi:poi-ooxml-lite:jar:5.0.0:compile org.apache.poi:poi-ooxml-schemas:jar:4.1.1:compile

两个包中都存在。但是两者的addNewFamily()方法实现方式不同。

调试过程中发现程序再本地使用的是org.apache.poi:poi-ooxml-lite:jar包中的CTFont,这时生成Excel并无异常。猜测可能是线上偶尔执行了poi-ooxml-schemas包下的CTFont导致的异常。

为了验证这个猜想,通过在本地强制修改org.apache.poi:poi-ooxml-lite:jar:5.0.0:compile包内容后,让程序固定使用poi-ooxml-schemas:jar下的CTFont类,稳定复现出了上述的问题。所以归根结底还是依赖的版本有冲突。

4.3 彻底修复

既然是依赖版本有冲突,那解决的方式就从版本匹配入手,这一次不再盲目的修改,参考了源码中的版本依赖关系,参考中央仓库中easypoi4.5.0版本的pom配置( https://repo1.maven.org/maven2/cn/afterturn/easypoi/4.5.0/easypoi-4.5.0.pom )



于是最终将项目中的pom依赖配置为easyapi4.5.0+apache-poi4.1.1版本,上线验证后问题彻底解决

4.4 遗留的困惑

上述问题中提到:org.openxmlformats.schemas.spreadsheetml.x2006.main.CTFont这个类在org.apache.poi:poi-ooxml-lite:jar:5.0.0:compile 和 org.apache.poi:poi-ooxml-schemas:jar:4.1.1:compile两个包都存在

按照mvn的依赖管理机制,不管是优先加载机制还是就近路径机制,应该都会只使用其中一个固定的包,本地调试是也一直使用poi-ooxml-lite.jar中的CTFont,但是线上偶现的失败场景下,似乎两个包下的类都会用到,此处不确定是否在存在嵌套复杂依赖的场景下,对于类加载的顺序是否会存在随机性,期待有了解的大佬希望能给解答一下。



五、致谢

最后,特别感谢信总、磊哥在此问题定位过程中给予的支持和耐心的答疑!

相关推荐

# Python 3 # Python 3字典Dictionary(1)

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值(key=>value)对用冒号(:)分割,每个对之间用逗号(,)分割,整个字典包括在花括号({})中,格式如...

Python第八课:数据类型中的字典及其函数与方法

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值...

Python中字典详解(python 中字典)

字典是Python中使用键进行索引的重要数据结构。它们是无序的项序列(键值对),这意味着顺序不被保留。键是不可变的。与列表一样,字典的值可以保存异构数据,即整数、浮点、字符串、NaN、布尔值、列表、数...

Python3.9又更新了:dict内置新功能,正式版十月见面

机器之心报道参与:一鸣、JaminPython3.8的热乎劲还没过去,Python就又双叒叕要更新了。近日,3.9版本的第四个alpha版已经开源。从文档中,我们可以看到官方透露的对dic...

Python3 基本数据类型详解(python三种基本数据类型)

文章来源:加米谷大数据Python中的变量不需要声明。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。在Python中,变量就是变量,它没有类型,我们所说的"类型"是变...

一文掌握Python的字典(python字典用法大全)

字典是Python中最强大、最灵活的内置数据结构之一。它们允许存储键值对,从而实现高效的数据检索、操作和组织。本文深入探讨了字典,涵盖了它们的创建、操作和高级用法,以帮助中级Python开发...

超级完整|Python字典详解(python字典的方法或操作)

一、字典概述01字典的格式Python字典是一种可变容器模型,且可存储任意类型对象,如字符串、数字、元组等其他容器模型。字典的每个键值key=>value对用冒号:分割,每个对之间用逗号,...

Python3.9版本新特性:字典合并操作的详细解读

处于测试阶段的Python3.9版本中有一个新特性:我们在使用Python字典时,将能够编写出更可读、更紧凑的代码啦!Python版本你现在使用哪种版本的Python?3.7分?3.5分?还是2.7...

python 自学,字典3(一些例子)(python字典有哪些基本操作)

例子11;如何批量复制字典里的内容2;如何批量修改字典的内容3;如何批量修改字典里某些指定的内容...

Python3.9中的字典合并和更新,几乎影响了所有Python程序员

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

Python3大字典:《Python3自学速查手册.pdf》限时下载中

最近有人会想了,2022了,想学Python晚不晚,学习python有前途吗?IT行业行业薪资高,发展前景好,是很多求职群里严重的香饽饽,而要进入这个高薪行业,也不是那么轻而易举的,拿信工专业的大学生...

python学习——字典(python字典基本操作)

字典Python的字典数据类型是基于hash散列算法实现的,采用键值对(key:value)的形式,根据key的值计算value的地址,具有非常快的查取和插入速度。但它是无序的,包含的元素个数不限,值...

324页清华教授撰写【Python 3 菜鸟查询手册】火了,小白入门字典

如何入门学习python...

Python3.9中的字典合并和更新,了解一下

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

python3基础之字典(python中字典的基本操作)

字典和列表一样,也是python内置的一种数据结构。字典的结构如下图:列表用中括号[]把元素包起来,而字典是用大括号{}把元素包起来,只不过字典的每一个元素都包含键和值两部分。键和值是一一对应的...

取消回复欢迎 发表评论:

请填写验证码