December 25, 2019
By: Damon

Jar文件中的资源

  1. 问题描述
  2. 问题解决
  3. 总结发言

问题描述

一般是这样的: 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的模板中, 实现完内容赋值后, 再把之前的临时文件删除掉, 然后再把流返回, 这样就可以实现浏览器下载文件了.

总结发言

  1. 开发模式中, resource目录是项目的资源目录, 按照约定, 是可以用clojure.java.io/resource访问的
  2. 开发时候放到resource目录下的文件会被打包到jar中, 打包在jar中的资源必须用clojure.java.io/resource读入, 不能直接用文件路径读入
  3. 结论就是resouce中的内容一定要使用resource函数访问, 用路径+文件的方式只在开发模式好用. 这是个bug!!!
  4. jar中的资源是只读的, 要想写入, 一定要copy(或者说extract)出来再用.
  5. 此外, 注意操作系统差异, 比如说macos上文件不区分大小写, windows上则区分大小写.
Tags: clojure