从 jar 中读取资源文件

Posted

技术标签:

【中文标题】从 jar 中读取资源文件【英文标题】:Reading a resource file from within jar 【发布时间】:2013-12-21 18:13:28 【问题描述】:

我想像这样从我的 jar 中读取资源:

File file;
file = new File(getClass().getResource("/file.txt").toURI());
BufferedReader reader = new BufferedReader(new FileReader(file));

//Read the file

在 Eclipse 中运行时它工作正常,但如果我将它导出到 jar 中,然后运行它,则会出现 IllegalArgumentException:

Exception in thread "Thread-2"
java.lang.IllegalArgumentException: URI is not hierarchical

我真的不知道为什么,但是通过一些测试我发现如果我改变了

file = new File(getClass().getResource("/file.txt").toURI());

file = new File(getClass().getResource("/folder/file.txt").toURI());

然后它的工作原理相反(它在 jar 中工作,但在 eclipse 中不工作)。

我正在使用 Eclipse,我的文件所在的文件夹位于类文件夹中。

【问题讨论】:

如果你想从 jar 中的目录中读取任何文件编号的文件,请参阅***-Link 我不确定最初的问题是否涉及 Spring。上一条评论中的链接是指来自不同问题的 Spring 特定答案。我相信getResourceAsStream 仍然是解决问题的更简单、更便携的解决方案。 【参考方案1】:

与其尝试将资源作为File 来处理,不如让ClassLoader 为资源返回InputStream,而不是通过getResourceAsStream:

try (InputStream in = getClass().getResourceAsStream("/file.txt");
    BufferedReader reader = new BufferedReader(new InputStreamReader(in))) 
    // Use resource

只要file.txt 资源在类路径上可用,那么无论file.txt 资源是在classes/ 目录中还是在jar 中,此方法都将以相同的方式工作。

URI is not hierarchical 出现是因为 jar 文件中资源的 URI 看起来像这样:file:/example.jar!/file.txt。您无法读取 jarzip 文件)中的条目,就像它是一个普通的旧 File。

以下问题的答案很好地解释了这一点:

How do I read a resource file from a Java jar file? Java Jar file: use resource errors: URI is not hierarchical

【讨论】:

谢谢,这很有帮助,代码运行良好,但我确实有一个问题,我需要确定InputStream 是否存在(如File.exists()),以便我的游戏可以判断是否是否使用默认文件。谢谢。 哦,顺便说一句,getClass().getResource("**/folder**/file.txt") 成功的原因是因为我将该文件夹与我的 jar 放在同一目录中:)。 getResourceAsStream 如果资源不存在则返回 null 以便您的“存在”测试。 当然...不要忘记关闭 inputStream 和 BufferedReader You cannot read the entries within a jar (a zip file) like it was a plain old File. 这很糟糕,因为有大量的库函数需要文件路径作为输入。【参考方案2】:

如果你想作为文件读取,相信还有类似的解决方案:

    ClassLoader classLoader = getClass().getClassLoader();
    File file = new File(classLoader.getResource("file/test.xml").getFile());

【讨论】:

URL.getFile() 将 URL 转换为文件名。它返回主机之后的 URL 部分,所有百分比编码都完好无损,因此如果路径包含任何非 ASCII 字符或 URL 中不允许的任何 ASCII 字符(包括空格),则结果将不是现有文件名,即使 URL 是 file: URL。 一旦程序构建到 jar 中,这在内部不起作用 不能从 jar 中工作,除非您先转换为字符串并在本地保存。【参考方案3】:

要访问 jar 中的文件,您有两种选择:

将文件放在与你的包名匹配的目录结构中(解压.jar文件后,应该和.class文件在同一目录下),然后使用getClass().getResourceAsStream("file.txt")访问它

将文件放在根目录(解压.jar文件后,应该在根目录),然后使用Thread.currentThread().getContextClassLoader().getResourceAsStream("file.txt")访问它

jar 用作插件时,第一个选项可能不起作用。

【讨论】:

非常感谢您的出色回答...第二个选项的另一个好处是它也适用于 main 方法,因为 getClass() 仅适用于实例而不适用于静态方法。 【参考方案4】:

确保使用正确的分隔符。我用File.separator 替换了相对路径中的所有/。这在 IDE 中运行良好,但在构建 JAR 中不起作用。

【讨论】:

【参考方案5】:

如果你使用的是spring,那么你可以使用下面的方法从src/main/resources中读取文件:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.springframework.core.io.ClassPathResource;

  public String readFileToString(String path) throws IOException 

    StringBuilder resultBuilder = new StringBuilder("");
    ClassPathResource resource = new ClassPathResource(path);

    try (
        InputStream inputStream = resource.getInputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) 

      String line;

      while ((line = bufferedReader.readLine()) != null) 
        resultBuilder.append(line);
      

    

    return resultBuilder.toString();
  

【讨论】:

欢迎来到 SO。这没有提供问题的答案。一旦你有足够的reputation,你就可以在任何帖子上comment。还要检查这个what can I do instead。 这将删除文件中的换行符!【参考方案6】:

我之前遇到过这个问题,我为加载做了后备方式。基本上第一种方式在 .jar 文件中工作,第二种方式在 eclipse 或其他 IDE 中工作。

public class MyClass 

    public static InputStream accessFile() 
        String resource = "my-file-located-in-resources.txt";

        // this is the path within the jar file
        InputStream input = MyClass.class.getResourceAsStream("/resources/" + resource);
        if (input == null) 
            // this is how we load file within editor (eg eclipse)
            input = MyClass.class.getClassLoader().getResourceAsStream(resource);
        

        return input;
    

【讨论】:

对我不起作用 ....我在 jar 的资源中有 .wsdl ...但它仍然无法解析 wsdl【参考方案7】:

到目前为止(2017 年 12 月),这是我发现的唯一一个在 IDE 内部和外部都工作的解决方案。

使用PathMatchingResourcePatternResolver

注意:它也适用于 spring-boot

在此示例中,我正在读取位于 src/main/resources/my_folder 中的一些文件:

try 
    // Get all the files under this inner resource folder: my_folder
    String scannedPackage = "my_folder/*";
    PathMatchingResourcePatternResolver scanner = new PathMatchingResourcePatternResolver();
    Resource[] resources = scanner.getResources(scannedPackage);

    if (resources == null || resources.length == 0)
        log.warn("Warning: could not find any resources in this scanned package: " + scannedPackage);
    else 
        for (Resource resource : resources) 
            log.info(resource.getFilename());
            // Read the file content (I used BufferedReader, but there are other solutions for that):
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
            String line = null;
            while ((line = bufferedReader.readLine()) != null) 
                // ...
                // ...                      
            
            bufferedReader.close();
        
    
 catch (Exception e) 
    throw new Exception("Failed to read the resources folder: " + e.getMessage(), e);

【讨论】:

【参考方案8】:

您可以使用从类路径读取的类加载器作为根路径(开头不带“/”)

InputStream in = getClass().getClassLoader().getResourceAsStream("file.txt"); 
BufferedReader reader = new BufferedReader(new InputStreamReader(in));

【讨论】:

【参考方案9】:

由于某种原因,当我将 Web 应用程序部署到 WildFly 14 时,classLoader.getResource() 总是返回 null。从 getClass().getClassLoader()Thread.currentThread().getContextClassLoader() 获取 classLoader 返回 null。

getClass().getClassLoader() API 文档说,

“返回类的类加载器。某些实现可能使用 null 来表示引导类加载器。如果此类是由引导类加载器加载的,则此方法将在此类实现中返回 null。”

如果您正在使用 WildFly 和您的 Web 应用程序,可以试试这个

request.getServletContext().getResource() 返回了资源 url。这里的request是ServletRequest的一个对象。

【讨论】:

【参考方案10】:

问题是某些第三方库需要文件路径名而不是输入流。大多数答案都没有解决这个问题。

在这种情况下,一种解决方法是将资源内容复制到临时文件中。以下示例使用 jUnit 的TemporaryFolder

    private List<String> decomposePath(String path)
        List<String> reversed = Lists.newArrayList();
        File currFile = new File(path);
        while(currFile != null)
            reversed.add(currFile.getName());
            currFile = currFile.getParentFile();
        
        return Lists.reverse(reversed);
    

    private String writeResourceToFile(String resourceName) throws IOException 
        ClassLoader loader = getClass().getClassLoader();
        InputStream configStream = loader.getResourceAsStream(resourceName);
        List<String> pathComponents = decomposePath(resourceName);
        folder.newFolder(pathComponents.subList(0, pathComponents.size() - 1).toArray(new String[0]));
        File tmpFile = folder.newFile(resourceName);
        Files.copy(configStream, tmpFile.toPath(), REPLACE_EXISTING);
        return tmpFile.getAbsolutePath();
    

【讨论】:

这被掩盖了很多。感谢您记录它。我很好奇如果没有 JUnit,还有哪些其他选择。 由于resourceName 应该总是使用/ 作为分隔符,不像decomposePath 内部的File 操作使用系统特定的文件分隔符,该方法不仅不必要地复杂path.split("/"),甚至是不正确的。此外,不清楚为什么在不使用其结果时调用.toArray(new String[0])) folder 是什么,Lists 来自哪个库,REPLACE_EXISTING 是什么...?请提供有关您帖子的完整信息。【参考方案11】:

以下代码适用于 Spring boot(kotlin):

val authReader = InputStreamReader(javaClass.getResourceAsStream("/file1.json"))

【讨论】:

【参考方案12】:

我找到了解决办法

BufferedReader br = new BufferedReader(new InputStreamReader(Main.class.getResourceAsStream(path)));

将“Main”替换为您编写的 java 类。将“path”替换为 jar 文件中的路径。

例如,如果您将 State1.txt 放在包 com.issac.state 中,则如果您运行 Linux 或 Mac,则键入路径为“/com/issac/state/State1”。如果您运行 Windows,则键入路径为“\com\issac\state\State1”。除非出现 File not found 异常,否则不要将 .txt 扩展名添加到文件中。

【讨论】:

【参考方案13】:

此代码在 Eclipse 和 Exported Runnable JAR 中都有效

private String writeResourceToFile(String resourceName) throws IOException 
    File outFile = new File(certPath + File.separator + resourceName);

    if (outFile.isFile())
        return outFile.getAbsolutePath();
    
    InputStream resourceStream = null;
    
    // Java: In caso di JAR dentro il JAR applicativo 
    URLClassLoader urlClassLoader = (URLClassLoader)Cypher.class.getClassLoader();
    URL url = urlClassLoader.findResource(resourceName);
    if (url != null) 
        URLConnection conn = url.openConnection();
        if (conn != null) 
            resourceStream = conn.getInputStream();
        
    
    
    if (resourceStream != null) 
        Files.copy(resourceStream, outFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
        return outFile.getAbsolutePath();
     else 
        System.out.println("Embedded Resource " + resourceName + " not found.");
    
    
    return "";
   

【讨论】:

这里的certPath 是什么?【参考方案14】:

就我而言,我终于成功了

import java.lang.Thread;
import java.io.BufferedReader;
import java.io.InputStreamReader;

final BufferedReader in = new BufferedReader(new InputStreamReader(
      Thread.currentThread().getContextClassLoader().getResourceAsStream("file.txt"))
); // no initial slash in file.txt

【讨论】:

奇怪的是,这也是我使用嵌套资源的解决方案。我想知道为什么它似乎与其他发布的答案中的斜线一起使用。 @ElectRocnic 我认为这是因为路径的解释方式。 “/file.txt”表示,切换到当前目录并打开file.txt,而“file.txt”表示,从当前位置打开file.txt。它们都通向同一个位置。对我来说,两者都适用于 Windows 系统。 啊哈,它在 docker+linux 环境中对我不起作用...感谢您的陈述 只是 getClass().getClassLoader().getResourceAsStream() 为我工作【参考方案15】:

我终于解决了错误:

String input_path = "resources\\file.txt";
        
        input_path = input_path.replace("\\", "/");  // doesn't work with back slash
        
        URL file_url = getClass().getClassLoader().getResource(input_path);
        String file_path = new URI(file_url.toString().replace(" ","%20")).getSchemeSpecificPart();
        InputStream file_inputStream = file_url.openStream();

【讨论】:

请考虑添加有关您的解决方案及其工作原理的更多信息

以上是关于从 jar 中读取资源文件的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Java jar 文件中读取资源文件?

如何从 Java jar 文件中读取资源文件?

怎么从外部读取jar包中的资源文件

如何从Java jar文件中读取资源文件?

Java从jar资源中读取文件

从jar包中读取资源