现象:
- 在web页面导入excel文件,excel中包含图片,在后台进行解析的时候,有时候会报NullPointerException。
问题排查:
- 由于POI里面并没有把这里的错误信息输出到日志文件,在服务器上报错后,从后台日志文件中根本看不出什么。
在本地调试的时候,又没有报错,搞得我们一直以为是临时文件的没有正常读取导致的,而且我们把tomcat的temp目录删除后重启,竟然神奇又不报错了。
哪知道昨天客户现场又报这个错,但是我刚刚删除了temp目录,重启了tomcat啊!!!受不了,我死活都要找到原因,说干就干,把线上的temp目录覆盖我本地的temp目录,
本地环境启起来,拿那个现场出错的excel一试,错误没有辜负我的期望,如期而遇,控制台错误日志如下(吐槽POI,这个错误日志竟然只输出控制台):
java.lang.NullPointerException
at org.apache.poi.ss.util.ImageUtils.getResolution(ImageUtils.java:117)
at org.apache.poi.ss.util.ImageUtils.getImageDimension(ImageUtils.java:79)
at org.apache.poi.ss.util.ImageUtils.setPreferredSize(ImageUtils.java:141)
at org.apache.poi.xssf.usermodel.XSSFPicture.getPreferredSize(XSSFPicture.java:221)
at org.apache.poi.xssf.usermodel.XSSFPicture.getPreferredSize(XSSFPicture.java:210)
at org.apache.poi.xssf.usermodel.XSSFPicture.getPreferredSize(XSSFPicture.java:200)
at com.hentre.all580.core.util.excel.ExcelUtilXlsx.getPicValue(ExcelUtilXlsx.java:51)
at com.hentre.all580.core.util.excel.ExcelUtil.getCellValue(ExcelUtil.java:118)
at com.hentre.all580.core.util.excel.ExcelHandler.getValue(ExcelHandler.java:155)
at com.hentre.all580.core.util.excel.JourneyExcelHandler.templateResolve(JourneyExcelHandler.java:64)
at com.hentre.all580.core.util.excel.JourneyExcelHandler.templateResolve(JourneyExcelHandler.java:115)
at com.hentre.all580.coreplatform.controller.upload.ImportItineraryHandler.doExecute(ImportItineraryHandler.java:41)
at com.hentre.all580.coreplatform.controller.upload.AbstractImportHandler.execute(AbstractImportHandler.java:41)
at com.hentre.all580.coreplatform.controller.upload.BaseUploadController.upload(BaseUploadController.java:184)
...
- 找到报错地方的代码如下:
public static int[] getResolution(ImageReader r) throws IOException {
int hdpi = 96;
int vdpi = 96;
double mm2inch = 25.4D;
Element node = (Element)r.getImageMetadata(0).getAsTree("javax_imageio_1.0"); //这一行抛错
NodeList lst = node.getElementsByTagName("HorizontalPixelSize");
if(lst != null && lst.getLength() == 1) {
hdpi = (int)(mm2inch / (double)Float.parseFloat(((Element)lst.item(0)).getAttribute("value")));
}
lst = node.getElementsByTagName("VerticalPixelSize");
if(lst != null && lst.getLength() == 1) {
vdpi = (int)(mm2inch / (double)Float.parseFloat(((Element)lst.item(0)).getAttribute("value")));
}
return new int[]{hdpi, vdpi};
}
- 从上面的现象看,一直以为是临时文件的读取问题,反复调试临时文件的创建和读取,此处略过10000字,poi里面解析图片的代码复杂。。。。。
- 偶然在调试在上面抛错那里的代码时,发现r的类型竟然是不一样的,正常的情况下是JPEGImageReader,报错的情况竟然是CMYKJPEGImageReader。
找到CMYKJPEGImageReader的getImageMetadata()方法,代码如下:
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
return null;
}
- 瞬间明白了,但是这坑爹的poi怎么会把图片解析成这个类型呢?为什么同一图片,有时候是,有时候又不是呢?百度了一下,大概说明如下:
CMYK也称作印刷色彩模式,顾名思义就是用来印刷的。
它和RGB相比有一个很大的不同:RGB模式是一种发光的色彩模式,你在一间黑暗的房间内仍然可以看见屏幕上的内容;
CMYK是一种依靠反光的色彩模式,我们是怎样阅读报纸的内容呢?是由阳光或灯光照射到报纸上,再反射到我们的眼中,才看到内容。它需要有外界光源,如果你在黑暗房间内是无法阅读报纸的。
只要在屏幕上显示的图像,就是RGB模式表现的。只要是在印刷品上看到的图像,就是CMYK模式表现的。比如期刊、杂志、报纸、宣传画等,都是印刷出来的,那么就是CMYK模式的了。
总结:
- 发现如果用POI从excel中获取图片的话,那么上面的问题是无解的。咱惹不起,只能躲了,果断在导入模板里面把图片删除了,然后把这个原则深深的记在了脑海里!
我真的是太机智了。。。