专栏首页Java深度编程框架源码调试实战之easypoi异常解决方案精讲
原创

框架源码调试实战之easypoi异常解决方案精讲

最近有个同事遇到了个棘手的问题,easypoi导出文件出了bug,却不知道是怎么回事,无从下手,无可奈何,由于事态紧急,只能火急火急的求助于我。我问他:“开发的时候功能调通了吗?测试阶段通过了吗?” 同事均回答:“之前测试都没有问题,之前的账号数据可以导出,却唯独是这个不行。我仔细看了我写的代码,根本就不觉得有什么问题啊,不知道原因出在哪里……”

导出的错误文件如下:

直接导出了异常信息,并未输出包装的行和列的数据

正常导出的文件应该是这样的:

正常情况下导出的数据

在详细了解情况以后我便开始了我的源码探究之路。确实,很多人在遇到此类问题的时候往往想到的就是自己的代码有问题,或者是使用框架不当,未按框架的规则来编写代码才导致出现问题,而极少会想到是自己使用的框架本身就有问题,本身就有bug。要知道框架也是人写的,是人就会犯错!更何况像easypoi是近年来才出现的新型框架,还不太成熟,潜在的bug多,出错的概率就更大些。

我帮人解决问题的同时,我习惯性的是希望帮助人学习到解决问题的能力,而不是仅仅解决这个问题。正所谓授人以鱼不如授人以渔,所以我便亲自在这位向我求助的同事面前掩饰了一番,如何去解决这个问题。

首先,我们将导出正确,和导出错误的两组参数进行收集,然后使用postMan分别进行调制,在关键代码初打上断点,如下图:

web层接口
service业务层
我封装的导出公共组件ExcelUtils
单sheet文件导出的核心关键代码

从上图可以看出,导出的关键代码在workbook.write(outputStream),于是我先执行能正常导出的debug,教同事用正常的参数去逐步熟悉整体运行的过程;然后我再执行第二遍debug,这时候传入错误导出的参数,当执行到关键代码处的时候观察两者的区别,从而判断问题所在。经过调试,我发现错误的代码再执行到单sheet时,执行代码

Sheet sheet = workbook.getSheet(param.getExportParams().getSheetName());

结果获取到了一个空对象,然后正常能导入时不时空对象,如下:

正常能导出时sheet对象是有值的
错误导出时sheet对象为null

那么问题就出在这里,正是由于行对象sheet对象为null才导致了后面的报错。

由于sheet对象为null才导致了下面的空指针异常

这时候就应该再进入更深层次的代码,探究为何传入为何有时候能获取到sheet对象,有时候却不行。

我们点开 workbook.getSheet(param.getExportParams().getSheetName())方法,继续打断点:

打开源码方法时,需要选择方法的调试界面

打开源码方法时,常出现的需要选择进入那个实现方法,这时候很多初级程序员就很懵逼,我到底应该进去那个方法啊?有可能会一时半会摸不着头脑,只能一个试。其实这也是有技巧的,这个技巧就是追溯对象的源头。上面是使用workbook调用的,我们往上寻找,是如何得到这个对象的,代码在

 //单sheet导出
 workbook = ExcelExportUtil.exportExcel(param.getExportParams(), param.getClazz() , param.getList());

那么我们再进入ExcelExportUtil.exportExcel()方法,如下:

从上可以看出workbook对象是XSSFWorkbook类型的,那么我们使用workerbook对象调方法的时候调的就必定是XSSFWorkbook下的方法,于是就知道了进入workbook.getSheet(param.getExportParams().getSheetName())方法应该选择第三个XSSFWorkbook类型的。其实除了此方式外,还可以再往前追溯入参,往往也能找到答案,本例子中的前端封装参数的时候是指定了ExcelType的类型的,如下图:

通过以上两种途径便可以知道框架的源码方法,众多实现中究竟应该调用那一个源码方法,快速定位方法,能大大节约一个个尝试的时间成本。

接着我们进入gerSheet()方法进一步调试:

我们可以看到sheet页对象的名称和传入的名称不相等,迭代器没有下一个值,于是便return null了:

条件成立,再次进入循环得到了返回了null对象

而正常参数下是能够直接获取到sheet对象的:

正常参数下,判断为false,不会再次进入do while循环中

整个过程的逻辑如下:1.首先创建了迭代器;2.执行了一次do……while循序,在循环中判断迭代器是否还有下一个值,第一次的时候有下一个值于是没有返回null,而是创建了sheet对象;3.第一次循环执行完毕后,才开始判断条件(do……while循环是先执行一次循环,再判断条件),这时候入参名称和sheet的名称相同,取反后便不成立,于是返回了有值的对象,反之则再次进入了循环,这时候迭代器已经没有下一个值了,于是就返回了null。

既然知道了以上的逻辑,那么就已经定位清楚问题了,问题的根源就在于名称不相等了,为啥名称不相等了呢? 这时候我们仔细观察两个名称的值,发现sheet的名称和入参名称name相比,是取了name的前31位字符,为什么会这样子呢?

我们打开.xlsx文档,输入页码名称,发现也是最大只能输入31个字符的,如下图

xlsx文档限制了只能输入31个字符

原来是因为xlsx文档限制了最大的页码名称只能输入31个字符,所以框架自动截取了前31个字符。这坑爹的框架也不说处理全面一点,留下了这个bug坑苦了广大程序员,哈哈……

既然知道了这个问题,那么如何修复这个框架的bug了? 按理来说这是框架的bug,应该改框架的源码最正确,可这样得反编译后,修改编码了再打包进去,很费时费力。而在入参时每个都做判断会增大代码量,也容易忽视这个问题。介于我已经封装了一个公用的导出组件类,那么我的思路是在公共组件进行处理。

将思路告诉向我求助的同事后,他便写了下面的代码:

    //单sheet页面设置样式
     String sheetName = param.getExportParams().getSheetName();
     if(sheetName.length() > 31) {
         sheetName = sheetName.substring(0,31);
     }
     Sheet sheet = workbook.getSheet(sheetName)

我就点评说这样写并不好,因为你只是把入参的值给改了,但是原参数param中依然保留了旧的,可能出现问题的参数,依然容易引发其它问题,这是一个变成思想的问题,代码应该这么写:

//单sheet页面设置样式
    //生成Sheet和提示信息
    //xlsx文件的sheet页名称最大只支持31个长度,当传参sheet名称长度>31时,将会无法获取sheet对象,所以需要截取
    String sheetName = param.getExportParams().getSheetName();
    if(sheetName.length() > 31) {
         param.getExportParams().setSheetName(sheetName.substring(0,31));
    }
    Sheet sheet = workbook.getSheet(param.getExportParams().getSheetName());

上面的这种写法才是正确的,更能体现变成思想能力,从源头消除隐患。

总结:经过此次框架的bug,相信遇到的人都应该明白了,不要太过于相信框架而质疑自己的代码有错,当你找不到自己的错误的时候,就该想想是不是自己使用的框架有问题了。

感谢大家的阅读,请关注微信公众号《Java深度编程》,谢谢大家!!!

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 框架源码调试实战之easypoi异常解决方案精讲

    最近有个同事遇到了个棘手的问题,easypoi导出文件出了bug,却不知道是怎么回事,无从下手,无可奈何,由于事态紧急,只能火急火急的求助于我。我问他...

    Java深度编程
  • Java1.8新特性 -- 接口默认方法

    以前经常会有面试题问接口里是否能写具体方法,现在依然还有些菜鸟再出这个面试题。Java 8 新增了接口的默认方法。简单说,默认方法就是接口可以有实现方法,而且不...

    Java深度编程
  • javaScript核心技术--“闭包”,不看绝对后悔!

    “闭包”,又称“定义在函数内部的函数”,闭包技术是javaScript中很关键的核心技术,很多框架的研发或者企业高端技术都需要使用到它。要理解闭包...

    Java深度编程
  • Laravel-Excel导出功能文档

    可以在闭包中修改一些属性,很多属性可在配置文件中设置默认值 config/excel.php

    Tayloryu
  • Python openpyxl : Ex

    通过调用方法load_workbook(filename)进行文件读取,该方法中还有一个read_only参数用于设置文件打开方式,默认为可读可写,该方法最终将...

    py3study
  • PowerBI将不同的excel文件的不同名的sheet汇总到一张表

    工作中经常会遇到收集各个分公司的表然后汇总到一张表的情况,PowerBI或powerquery中的”从文件夹获取数据“提供了很大的便利。

    陈学谦
  • Python数据持久化-csv、excel篇

    2018年7月4日笔记 学习目标: 1.会使用Python第三方模块操作CSV文件 2.会使用Python第三方模块操作EXCEL文件

    潇洒坤
  • 按部就班的吴恩达机器学习网课用于讨论(2)

    梯度下降的伪代码如左下,计算误差函数J的梯度,完成一次更新误差函数中的变参,使得误差函数的值尽量最小化。

    嘘、小点声
  • 《spss统计分析与行业应用案例详解》实例37重复测量方差分析

    重复测量方差分析可以考察观测指标是否会随着测量次数的增加而变化,以及是否会受时间的影响。

    统计学家
  • Flash AS3 性能优化

    http://help.adobe.com/zh_CN/as3/mobile/index.html

    py3study

扫码关注云+社区

领取腾讯云代金券