1、应用场景
由于以前我们开发的应用都是在一个Application之中,并且使用的war方式打包发版(SpringBoot版本)的。在最近的项目之中我们改造了成为微服务(SpringCloud版),于是为了方便发版,各个服务之间直接使用jar方式打包。说明一下 war打包和jar打包各有优缺点。
war打包可以在更新一个java class类的时候,直接复制一个class类即可,操作方便。如果不前后端分离,相关的静态文件可以可以直接复制更新。可以使得每次复制的代码量及其少。
jar打包方式可以其他不用管,每次都直接复制覆盖即可不用管其他内容。
参考文章:Jar/War/Ear等包的作用与区别详解
jar | war | EAR | |
英文 | Java Archive file | Web Archive file | Enterprise Archive file |
包含内容 | class、properties文件,是文件封装的最小单元;包含Java类的普通库、资源(resources)、辅助文件(auxiliary files)等 | Servlet、JSP页面、JSP标记库、JAR库文件、HTML/XML文档和其他公用资源文件,如图片、音频文件等 | 除了包含JAR、WAR以外,还包括EJB组件 |
部署文件 | application-client.xml | web.xml | application.xml |
容器 | 应用服务器(application servers) | 小型服务程序容器(servlet containers) | EJB容器(EJB containers) |
级别 | 小 | 中 | 大 |
2、中途过程记录
由于本次使用微服务(SpringCloud)方式,于是发布jar方式来使用。但是我们的业务使用使用到了Excel模板。我们在导出数据的时候,需要先从jar之中读取到Excel模板,然后把导出结果集List一条一条的写入到Excel模板之中。由于以前我们使用的是War方式打包,以及在IDE开发环境之中,没有打包为Jar包的时候是可以通过常规的Servlet方式获得文件路径,如下的代码所示:
String realPath = request.getServletContext().getRealPath("/app/exportTemplate/");
String templatePath = realPath + File.separator + templateNames[0]+".xlsx";;
此种方式在打包为War包和本地IDE之中未打包为Jar时候是可以正常运行,并导出数据。
当使用Jar方式打包的时候,此种方式就不起作用了,就导出不了数据,因为打包后的Jar是一个文件,里面包含了你的Excel文件和其他文件。此时需要如下两方面的注意事项:
2.1、打包jar时候包含你的Excel模板文件,以及其他资源文件
<build>
<finalName>project_module_8811</finalName>
<resources>
<resource>
<directory>src/main/java</directory>
<filtering>true</filtering>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
<include>**/*.json</include>
</includes>
</resource>
<!-- Excel 获得其他 OLE文件 假如如下使用方式-->
<resource>
<directory>src/main/webapp</directory>
<filtering>true</filtering>
<include>**/**/*.xlsx</include>
</resource>
</resources>
</bulid>
2.2、在Jar文件之中通过Stream方式获得其中包含的Excel模板或者其他文件
// 导出模板路径
String templatePath = "app/exportTemplate" + File.separator + templateNames[0]+ ".xlsx";
ClassPathResource resource = new ClassPathResource(templatePath);
resource.getInputStream();
如果要读取Jar之中的文件都需要使用此种流的方式获得文件。
可以参考如下文章:2.3、胜利前的黑暗
本人也是通过此种Stream方式把Excel模板以InputStream 传递给导出表的。并且还改造了一起的方法。如下代码所示
long millionSeconds = sdf.parse(str).getTime();// 毫秒
String fileName = templateNames[1] + millionSeconds + ".xlsx";
setExportResponseHeader(response, getValue(fileName));
ServletOutputStream ouputStream = response.getOutputStream();
export2ExcelByInputStream(excelInputStream, benmap, ouputStream);
@SuppressWarnings("rawtypes")
public static void export2ExcelByInputStream(InputStream excelInputStream, Map beans, OutputStream os) {
// 导出模板解析器
XLSTransformer transformer = new XLSTransformer();
try {
Workbook workbook = transformer.transformXLS(excelInputStream, beans);
// JxlsOutTag.localThd.remove();
workbook.write(os);
} catch (Exception e) {
e.printStackTrace();
}
}
如果使用通用方式 来包含Excel模板和Word模板或者使用OLE类型文件,会导致你崩溃的事情发生。
在发现此问题后,本人没有关注这个报错的原因,于是后面各种试验都不能导出数据,各种崩溃很无语的,就要在放弃了。最后差点让其他同事准备把这个Excel文件写死固定位置,以期待获得此模板信息。准备把读取模板的文件位置写死在配置文件之中,本打算通过,
配置文件的代码
#文件上传位置
cbs.imagesPath=file://83_mntdir/excelTemplate/
@Value("${cbs.imagesPath}")
private String webUploadPath;
但是此同事所写点的导出工具类之中所写代码都是static修饰的。代码如下:
@SuppressWarnings("rawtypes")
public static void export2Excel(String templatePath, Map beans, OutputStream os) {
// 导出模板解析器
XLSTransformer transformer = new XLSTransformer();
InputStream is = null;
/*try {
is = new FileInputStream(templatePath);
} catch (FileNotFoundException e1) {
e1.printStackTrace();
}*/
try {
ClassPathResource cpr = new ClassPathResource("/" + templatePath);
// is=new FileInputStream(file);
is = cpr.getInputStream();
} catch(Exception e1) {
e1.printStackTrace();
}
try {
Workbook workbook = transformer.transformXLS(is, beans);
// JxlsOutTag.localThd.remove();
workbook.write(os);
} catch (Exception e) {
e.printStackTrace();
}
}
因为这static修饰的代码在类加载时候已经被加载了,如果你一个非静态类型的变量是无法传递进去的,除非使用参数传递进去
于是这位同事写死位置失败了。
此时我们另外一位同事,发现的问题的根源,依据报错原因找到了为何不能通过InputStream把Excel模板文件通过数据流方式传递原因。
POI之java.io.IOException: ZIP entry size is too large
报错如下所示:
java.io.IOException: Failed to read zip entry source
at org.apache.poi.openxml4j.opc.ZipPackage.<init>(ZipPackage.java:106) ~[poi-ooxml-3.15.jar:3.15]
at org.apache.poi.openxml4j.opc.OPCPackage.open(OPCPackage.java:342) ~[poi-ooxml-3.15.jar:3.15]
at org.apache.poi.util.PackageHelper.open(PackageHelper.java:37) ~[poi-ooxml-3.15.jar:3.15]
at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:285) ~[poi-ooxml-3.15.jar:3.15]
at com.fish.cdc.data.std.controller.DemoController.importData(DemoController.java:51) ~[classes/:?]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_144]
.....................
Caused by: java.io.IOException: ZIP entry size is too large
at org.apache.poi.openxml4j.util.ZipInputStreamZipEntrySource$FakeZipEntry.<init>(ZipInputStreamZipEntrySource.java:122) ~[poi-ooxml-3.15.jar:3.15]
at org.apache.poi.openxml4j.util.ZipInputStreamZipEntrySource.<init>(ZipInputStreamZipEntrySource.java:56) ~[poi-ooxml-3.15.jar:3.15]
at org.apache.poi.openxml4j.opc.ZipPackage.<init>(ZipPackage.java:99) ~[poi-ooxml-3.15.jar:3.15]
---------------------
最后通过此篇文章分析 POI之java.io.IOException: ZIP entry size is too large
找到所有原因
Excel文件被损坏了, 考虑到maven项目中打开了filtering的特性,就是会针对项目的资源文件进行扫描,一般的文件,eot文件等等都会受到影响,从而导致了文件无法被正确的解析。
问题的解决
既然知道了原因,则就可以在filtering中新增exclude/include规则即可:
<resource>
<directory>src/main/webapp</directory>
<filtering>true</filtering>
<excludes>
<exclude>**/**/*.xlsx</exclude>
</excludes>
</resource>
<resource>
<directory>src/main/webapp</directory>
<filtering>false</filtering>
<includes>
<include>**/**/*.xlsx</include>
</includes>
</resource>
此段配置的意思,在Maven filter的过程之中先 excludes 排除掉eot文件(Excel模板文件)
在进过此种方式配置之后,都可以解决通过InputStream流方式在Jar之中的Excel模板之中写入数据了。
3、针对此次解决问题的反思
此问题花费了大改一天半时间,第一天下午主要发现了模板文件未被打包进入Jar文件,甚至本地IDE环境之中也没有此模板文件,最后通过找到以前项目把模板文件copy过来。第一步已经找到了Excel模板文件。
接着第二天进行通过InpuStream方式获获得Excel模板文件,但是忽略了报出的错误
java.io.IOException: Failed to read zip entry source
at org.apache.poi.openxml4j.opc.ZipPackage.
<init>(ZipPackage.java:106) ~[poi-ooxml-3.15.jar:3.15]
at org.apache.poi.openxml4j.opc.OPCPackage.open
(OPCPackage.java:342) ~[poi-ooxml-3.15.jar:3.15]
at org.apache.poi.util.PackageHelper.open
(PackageHelper.java:37) ~[poi-ooxml-3.15.jar:3.15]
at org.apache.poi.xssf.usermodel.XSSFWorkbook.<init>(XSSFWorkbook.java:285) ~[poi-ooxml-3.15.jar:3.15]
at com.fish.cdc.data.std.controller.DemoController.importData
(DemoController.java:51) ~[classes/:?]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_144]
导致在即将解决问题时候,差点让自己的成果付之东流。
研发和破案以及研究科学一样不能放过任何一个细节,只有细心才能发现问题的蛛丝马迹,通过分析这些关联关系才能够解决问题。
另外同事没有能够通过写死文件位置成功的主要原因是在静态(Static)方法的类里面获得常量信息是不可能的,只能通过传递参数方式还是可以的。
另外作为Java程序多多关注此网站