December 25, 2019
By: Damon
Jar文件中的资源
问题描述
一般是这样的: xxx文件(可以是excel模板, 字体文件, 等)在我xxx项目中开发时完全没问题, 一旦打包称jar文件, 执行的时候就找不到文件了!!!
举个例子:
前几天发现我本地运行没有问题的excel导出功能, 在测试环境上居然报错. 错误信息如下
Caused by: java.io.FileNotFoundException: File 'file:\C:\develop\workspaces\Clojure_Projects\customplatform\custombackend\target\uberjar\custombackend.jar!\excel-template\hb-coat-template.xlsx' does not exist
at com.alibaba.excel.util.FileUtils.openInputStream(FileUtils.java:95)
at com.alibaba.excel.util.FileUtils.readFileToByteArray(FileUtils.java:64)
at com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder.copyTemplate(WriteWorkbookHolder.java:171)
at com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder.<init>(WriteWorkbookHolder.java:130)
... 65 common frames omitted
以上错误信息显示, 我在使用excel模板导出信息的时候, 这个模板文件找不到. 测试服务器上是以jar包形式运行的, 而我本地是直接运行的, 我本地执行
lein uberjar
本地的jar运行, 复现了和服务器上一样的错误.
问题解决
如果本地直接运行, 是可以通过路径找到该文件的.
之前一些java web项目是打成war包, 运行后会自动解压, 所以也没有问题.
但是jar包在运行的时候, 是无法访问到jar里面的文件的.
在clojure中我们是使用 clojure.java.io/resource 来加载资源文件.
而且http://clojuredocs.org/clojure.java.io/resource,
明确说明了如何在运行的jar或者war文件中, copy二进制文件.
If you need to copy a binary file from a running JAR (or WAR),
don't call slurp as it will try and decode the file.
Instead, extract similarily to:
(with-open [in (io/input-stream (io/resource "file.dat"))] ;; resources/file.dat
(io/copy in (io/file "/path/to/extract/file.dat")))
上面的方法, 我们先把jar中的资源文件读入到流中, 然后再把内容写入到新建的文件中, 这样就实现了把资源文件中的模板, copy到jar的同级目录下.
我们来改造一下, 之前解压模板文件的方法, 比之前清爽了很多.
(defn- reader-template
"
读入模板
"
[t-path]
(let [resource (io/resource t-path)
file-name (str (common-utils/uuid) ".xlsx")]
(with-open [in (io/input-stream resource)]
(io/copy in (io/file file-name)))
(io/as-file file-name)))
(defn gen-excel-by-template
"
简单填充
指定模板和数据, 生成Excel
"
[t-path m]
(let [template-file (reader-template t-path)
out (java.io.ByteArrayOutputStream.)]
(-> (EasyExcel/write out)
(.withTemplate (reader-template t-path))
(.sheet)
(.doFill (java.util.HashMap. m)))
(io/delete-file template-file)
(io/input-stream (.toByteArray out))))
然后把新生成的文件作为参数传入到easyexcel的模板中, 实现完内容赋值后, 再把之前的临时文件删除掉, 然后再把流返回, 这样就可以实现浏览器下载文件了.
总结发言
- 开发模式中,
resource目录是项目的资源目录, 按照约定, 是可以用clojure.java.io/resource访问的 - 开发时候放到
resource目录下的文件会被打包到jar中, 打包在jar中的资源必须用clojure.java.io/resource读入, 不能直接用文件路径读入 - 结论就是
resouce中的内容一定要使用resource函数访问, 用路径+文件的方式只在开发模式好用. 这是个bug!!! - jar中的资源是只读的, 要想写入, 一定要copy(或者说extract)出来再用.
- 此外, 注意操作系统差异, 比如说macos上文件不区分大小写, windows上则区分大小写.