【问题标题】:Importing/using a resource from an external clojar从外部 clojar 导入/使用资源
【发布时间】:2015-06-16 02:11:05
【问题描述】:

我想要做的是在独立的 Maven 存储库/clojar 中打包一个大文件(MIDI 声音字体),然后能够以编程方式将其拉下并从单独的项目中使用它。事实证明,这个看似简单的任务比我预期的要复杂。

如果有一种方法可以直接访问这些资源,或者将它们公开为公共变量或其他方式,那么理想的情况是。这是我尝试的第一件事——我做了这样的事情:

(ns midi.soundfont.fluid-r3
  (:require [clojure.java.io :as io]))

(def sf2
  (io/file (io/resource "fluid-r3.sf2")))

但是,我遇到的问题是io/resource 只能在当前类路径上找到资源文件。一旦我尝试从另一个项目(或从 REPL)中要求这个命名空间,我得到:

java.lang.IllegalArgumentException: Not a file: jar:file:/Users/dave/.m2/repository/midi/soundfont/fluid-r3/midi.soundfont.fluid-r3/0.1.0/midi.soundfont.fluid-r3-0.1.0.jar!/fluid-r3.sf2

如果无法直接访问资源,我会很高兴有一个解决方案,包括将文件复制到文件系统中的某个路径。我也尝试过,但是在尝试从另一个项目运行“将文件复制到文件系统”方法时遇到了同样的问题——io/resource 仍然无法找到该文件,因为它不在当前的类路径中.

我发现了之前在SO上被问过的类似问题,比如:

然而,这些解决方案似乎只适用于复制作为 当前(正在运行的)项目中的资源的文件。

是否可以做这两件事之一?

  1. 从外部 clojar 访问资源文件
  2. 将资源文件导入到当前项目中,以便我可以使用io/resource访问它

【问题讨论】:

  • 将资源复制到文件中就是您的答案。正如错误消息所述,jar 内的资源不是文件。这不是类路径问题,这是一个基本事实,即 jar 中的资源不是文件。
  • “不是文件”似乎是 Java 告诉我它不能用不存在的资源制作文件的方式。运行(io/file (io/resource "fluid-r3.sf2"))midi.soundfont.fluid-r3 项目(其资源目录包含该文件)的类路径上运行时有效,但在任何其他项目中运行时会引发“不是文件”异常。
  • 澄清一下,我可以将资源复制到文件中。问题是从另一个项目中执行此操作。这有意义吗?
  • 这不是从资源中创建文件的方式。 io/file 的参数必须是可以用作文件的东西——jar 内的资源条目根本不符合条件。您可以从资源中读取并写入实际文件,如您帖子中的链接中所述。
  • 根据定义,资源是类路径上的东西。你可以啜饮资源。您可以从资源复制到字节数组或输出流或实际文件中。但是您不能简单地将资源强制为文件,除非您的 URI 指向磁盘上的某个位置。

标签: java maven clojure


【解决方案1】:

正如 dbasch 正确解释的那样,io/resource 返回的是 URL,而不是文件。但是为什么你能够打开那个 URL io/file 在 REPL 或 lein run 但不是来自罐子?这是因为第一种情况下的 URL 指向普通文件 在文件系统中,而使用 jar 运行时的 URL 指向 jar 内的资源,因此它不是正确的文件。

我在this github repo中做了一个例子。我将-main代码复制到这里以供参考:

(defn -main [& args]
  (let [r (io/resource "greet")]
    (println r)
    (println (slurp r))
    (with-open [rdr (io/reader r)]
      (println (clojure.string/join ", " (line-seq rdr))))
    (println (io/file r))))

lein run 运行显示:

› lein run
#<URL file:/home/nicolas/projects/clojure/resources/resources/greet>
hello
world

hello, world
#<File /home/nicolas/projects/clojure/resources/resources/greet>

运行 uberjar 节目:

› java -jar target/resources-0.1.0-SNAPSHOT-standalone.jar 
#<URL jar:file:/home/nicolas/projects/clojure/resources/target/resources-0.1.0-SNAPSHOT-standalone.jar!/greet>
hello
world

hello, world
Exception in thread "main" java.lang.IllegalArgumentException: Not a file: jar:file:/home/nicolas/projects/clojure/resources/target/resources-0.1.0-SNAPSHOT-standalone.jar!/greet
        at clojure.java.io$fn__8588.invoke(io.clj:63)
        at clojure.java.io$fn__8572$G__8556__8577.invoke(io.clj:35)

看看#&lt;URL file:/home/nico...#&lt;URL jar:file:/home/nico...之间的区别,这就解释了为什么你不能在上面调用(io/file),但是你可以用slurp阅读它或者用io/reader创建一个阅读器。

【讨论】:

    【解决方案2】:

    (io/resource "fluid-r3.sf2") 是一个 url,而不是一个文件。如果您想一次将其全部存储在内存中,您可以 slurp 它,或者使用 java.net.URL api 将其作为流读取(如果您愿意,可以在读取时将其写入文件)。

    例子:

    user> (type (clojure.java.io/resource "text.txt"))
    java.net.URL
    user> (slurp (clojure.java.io/resource "text.txt"))
    "this is a text file\n"
    

    【讨论】:

    • 我的问题不是如何复制文件,而是如何从另一个项目访问资源。如果我尝试访问的文件不在当前项目中,io/resource 将失败并出现“不是文件”异常。
    • 次要更正:如果当前资源路径上不存在该文件,则尝试在 (io/resource "fluid-r3.sf2") 上执行 io/fileslurp 等操作会失败并显示“不是文件”。
    • 如果它不在资源路径上,您的错误将是 IllegalArgumentException No implementation of method: :make-reader of protocol: #'clojure.java.io/IOFactory found for class: nil clojure.core/-cache-protocol-fn (core_deftype.clj:554) - 它不会提及任何关于文件的内容,因为关于 slurp 或资源的任何内容都不会暗示文件,并且 io/resource 返回 nil不在路径上的资源。
    • 嗯...有趣。 io/resource 确实返回了一个 URL,当我从另一个项目中尝试它时,但是当我尝试从资源中创建一个文件时,我得到“不是文件”。我想我只是误解了如何从资源中创建文件...(io/file (io/resource "path/to/file")) 一直对我有用,但仅当我尝试访问 的资源路径上的资源时当前 项目。从 external 项目(例如,作为依赖项拉入的 clojar)访问资源是否有一些技巧?
    • 您不能从 URL 中创建文件。您可以将 URL 下载到文件。我认为我的答案很清楚。
    【解决方案3】:

    “从外部 clojar 访问资源文件”

    我从this article 找到了这个解决方案。 Dmitri Sotnikov 在他的冷冻剂项目中做到了这一点。

    源代码是hereload-plugins 函数将从外部 clojars 的每个资源目录下检索所有 plugin.edn

    (ns cryogen-core.plugins
      (:require [clojure.edn :as edn]
                [clojure.string :as string]
                [text-decoration.core :refer :all]))
    
    (defn load-plugin [^java.net.URL url]
      (let [{:keys [description init]} (edn/read-string (slurp url))]
        (println (green (str "loading module: " description)))
        (-> init str (string/split #"/") first symbol require)
        ((resolve init))))
    
    (defn load-plugins []
      (let [plugins (.getResources (ClassLoader/getSystemClassLoader) "plugin.edn")]
        (doseq [plugin (enumeration-seq plugins)]
          (load-plugin (. ^java.net.URL plugin openStream)))))
    

    如果你不喜欢过多的 java 互操作,pomegranate library 中有一个 resources 函数可以检索外部 clojars 中具有给定名称的所有资源。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-13
      • 1970-01-01
      • 1970-01-01
      • 2021-10-29
      • 2014-03-05
      • 2014-02-28
      相关资源
      最近更新 更多