jar包内的文件对于Spring不可见

163

全部

我创建了一个包含以下 MANIFEST.MF 的 jar 文件:

Manifest-Version: 1.0
Ant-Version: Apache Ant 1.8.3
Created-By: 1.6.0_25-b06 (Sun Microsystems Inc.)
Main-Class: my.Main
Class-Path: . lib/spring-core-3.2.0.M2.jar lib/spring-beans-3.2.0.M2.jar
在其根目录下有一个名为my.config的文件,该文件在我的spring-context.xml中被引用如下:
<bean id="..." class="...">
    <property name="resource" value="classpath:my.config" />
</bean>
如果我运行JAR文件,除了加载特定文件的过程之外,一切看起来都很正常。
Caused by: java.io.FileNotFoundException: class path resource [my.config] cannot be resolved to absolute file path because it does not reside in the file system: jar:file:/D:/work/my.jar!/my.config
        at org.springframework.util.ResourceUtils.getFile(ResourceUtils.java:205)
    at org.springframework.core.io.AbstractFileResolvingResource.getFile(AbstractFileResolvingResource.java:52)
    at eu.stepman.server.configuration.BeanConfigurationFactoryBean.getObject(BeanConfigurationFactoryBean.java:32)
    at eu.stepman.server.configuration.BeanConfigurationFactoryBean.getObject(BeanConfigurationFactoryBean.java:1)
    at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:142)
    ... 22 more
  • 类从jar包内部加载
  • Spring和其他依赖项从不同的jar包中加载
  • Spring上下文被加载 (new ClassPathXmlApplicationContext("spring-context/applicationContext.xml"))
  • my.properties被加载到PropertyPlaceholderConfigurer ("classpath:my.properties")
  • 如果我将我的.config文件放在文件系统之外,并将资源URL更改为“file:”,一切似乎都很好...

有什么提示吗?

13个回答

291
如果你的spring-context.xml和my.config文件在不同的jar包中,那么你需要使用classpath*:my.config
更多信息这里 此外,请确保在从jar文件中加载时使用resource.getInputStream()而不是resource.getFile()

22
再次查看,你的一些调用代码(可能是BeanConfigurationFactoryBean)试图加载java.io.File。File指的是文件系统中的文件,而在jar包中的条目不是。调用代码应该使用resource.getInputStream来从jar包中加载。 - sbk
80
这就是答案。请注意,不要在使用资源文件时使用resource.getFile()函数,以免出现问题。谢谢! - BTakacs
3
不使用 getFile() 方法获取 jar 包内文件的原因是什么?是否因为文件在 Jar 包内,所以“文件”实际上是 Jar 文件本身? - RockMeetHardplace
14
一个java.io.File代表文件系统中的文件,位于目录结构中。Jar是一个java.io.File。但是,在该文件中的任何内容都超出了java.io.File的范围。就Java而言,在解压缩之前,JAR文件中的类与Word文档中的单词没有什么不同。 - sbk
4
除了使用resource.getInputStream()之外,您还可以使用resource.getURL()。 - Valeriy K.
显示剩余14条评论

84

在Spring Jar包中,我使用了新的ClassPathResource(filename).getFile()方法,但是抛出了以下异常:

不能解析为绝对文件路径,因为它不属于文件系统: jar

但是,使用new ClassPathResource(filename).getInputStream()方法可以解决这个问题。原因是JAR包中的配置文件不存在于操作系统的文件树中,所以必须使用getInputStream()方法。


1
这是正确的解决方案。 - Nimantha
你好,你知道如何在我的Spring应用程序在Docker容器中创建文件吗?如果你能回答我的问题,谢谢! - Sonn

70

我知道这个问题已经得到了解答。但是,对于那些使用Spring Boot的人来说,这个链接对我很有帮助- https://smarterco.de/java-load-file-classpath-spring-boot/

然而,resourceLoader.getResource("classpath:file.txt").getFile();引起了这个问题,而sbk的评论:

就是这样。java.io.File代表文件系统中的一个文件,在目录结构中。Jar是一个java.io.File。但是在那个文件之内的任何东西都超出了java.io.File的范围。就Java而言,在未压缩之前,jar文件中的类与Word文档中的单词没有区别。

帮助我理解了为什么要使用getInputStream()。现在对我有用了!

谢谢!


你好,你知道如何在我的Spring应用程序在Docker容器中创建文件吗?如果你能回答我的问题,谢谢! - Sonn

15

错误信息是正确的(虽然不太有帮助):我们正在尝试加载的文件不是文件系统中的文件,而是位于ZIP内部的字节块。

通过实验(Java 11,Spring Boot 2.3.x),我发现这可以在不更改任何配置甚至不使用通配符的情况下工作:

var resource = ResourceUtils.getURL("classpath:some/resource/in/a/dependency");
new BufferedReader(
  new InputStreamReader(resource.openStream())
).lines().forEach(System.out::println);

1
这正是问题所在!谢谢! - Louie Bafford
谢谢!这非常有帮助!在Liberty服务器上运行时需要。 - Tal Haham
@Sonn请通过https://stackoverflow.com/questions/ask发布新问题。 - Raphael

3

我在我的Spring应用程序中遇到了递归加载资源的问题,并发现问题是我应该使用resource.getInputStream。下面是一个示例,展示如何递归读取config/myfiles目录下的所有json文件。

Example.java

private String myFilesResourceUrl = "config/myfiles/**/";
private String myFilesResourceExtension = "json";

ResourceLoader rl = new ResourceLoader();

// Recursively get resources that match. 
// Big note: If you decide to iterate over these, 
// use resource.GetResourceAsStream to load the contents
// or use the `readFileResource` of the ResourceLoader class.
Resource[] resources = rl.getResourcesInResourceFolder(myFilesResourceUrl, myFilesResourceExtension);

// Recursively get resource and their contents that match. 
// This loads all the files into memory, so maybe use the same approach 
// as this method, if need be.
Map<Resource,String> contents = rl.getResourceContentsInResourceFolder(myFilesResourceUrl, myFilesResourceExtension);

ResourceLoader.java

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.StreamUtils;

public class ResourceLoader {
  public Resource[] getResourcesInResourceFolder(String folder, String extension) {
    ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
    try {
      String resourceUrl = folder + "/*." + extension;
      Resource[] resources = resolver.getResources(resourceUrl);
      return resources;
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  public String readResource(Resource resource) throws IOException {
    try (InputStream stream = resource.getInputStream()) {
      return StreamUtils.copyToString(stream, Charset.defaultCharset());
    }
  }

  public Map<Resource, String> getResourceContentsInResourceFolder(
      String folder, String extension) {
    Resource[] resources = getResourcesInResourceFolder(folder, extension);

    HashMap<Resource, String> result = new HashMap<>();
    for (var resource : resources) {
      try {
        String contents = readResource(resource);
        result.put(resource, contents);
      } catch (IOException e) {
        throw new RuntimeException("Could not load resource=" + resource + ", e=" + e);
      }
    }
    return result;
  }
}

你好,你知道如何在我的Spring应用程序在Docker容器中创建文件吗?如果你能回答我的问题,谢谢! - Sonn

2
对于Kotlin用户,我是这样解决的:
val url = ResourceUtils.getURL("classpath:$fileName")
val response = url.openStream().bufferedReader().readText()

这对我很有效。如果文件在文件夹中,则必须引用文件夹和文件名,例如 /data/myfile.json - Mfuon Leonard

2

当我使用Tomcat6.x时,遇到了类似的问题,但是我找到的所有建议都没有帮助。最后,我删除了Tomcat的work文件夹,问题就解决了。

我知道这看起来不合逻辑,但出于文档目的...


1
在Spring Boot 1.5.22.RELEASE的Jar打包中,以下方法适用于我:
```` // 代码示例 ````
InputStream resource = new ClassPathResource("example.pdf").getInputStream();
            

"

“example.pdf”位于src/main/resources中。

然后将其读取为byte[]。

"
FileCopyUtils.copyToByteArray(resource);

你好,你知道如何在我的Spring应用程序在Docker容器中创建文件吗?如果你能回答我的问题,谢谢! - Sonn

1

@sbk的回答是我认为在Spring Boot环境中应该采用的方式(除了@Value("${classpath*:}")). 但在我的情况下,如果从独立的jar文件中执行,它并没有起作用..也许是我做错了什么。

但这可以是另一种做法,

InputStream is = this.getClass().getClassLoader().getResourceAsStream(<relative path of the resource from resource directory>);

你好,你知道如何在我的Spring应用程序在Docker容器中创建文件吗?如果你能回答我的问题,谢谢! - Sonn

1

我遇到了一个比较复杂的问题,因为我有多个同名文件,其中一个在主Spring Boot jar中,其他的在主fat jar中的jar文件中。 我的解决方案是获取所有同名资源,然后通过包名过滤获取我需要的那个文件。 获取所有文件的方法:

ResourceLoader resourceLoader = new FileSystemResourceLoader();
final Enumeration<URL> systemResources = resourceLoader.getClassLoader().getResources(fileNameWithoutExt + FILE_EXT);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接