【问题标题】:File inside jar is not visible for springjar 内的文件对于 spring 不可见
【发布时间】:2013-01-30 08:57:51
【问题描述】:

全部

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

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:”,一切似乎都很好......

有什么建议吗?

【问题讨论】:

    标签: spring jar classpath


    【解决方案1】:

    如果您的 spring-context.xml 和 my.config 文件位于不同的 jar 中,那么您将需要使用 classpath*:my.config?

    更多信息here

    另外,请确保在从 jar 文件中加载时使用的是 resource.getInputStream() 而不是 resource.getFile()

    【讨论】:

    • 再看一遍,您的一些调用代码(可能是 BeanConfigurationFactoryBean)正在尝试加载 java.io.File。文件是指文件系统上的文件,而 jar 中的条目不是。调用代码应该使用 resource.getInputStream 而不是从 jar 中加载。
    • ...这就是答案...谢谢!在 jar 内不要使用 resource.getFile() :-)
    • 是否有“为什么”的可能?在 jar 中不使用 getFile() 的背后?是否只是文件在 Jar 内部,因此“文件”是 jar 文件??
    • 就是这样。 java.io.File 代表文件系统上的一个文件,在一个目录结构中。 Jar 是一个 java.io.File。但是该文件中的任何内容都超出了 java.io.File 的范围。就java而言,在解压之前,jar文件中的类与word文档中的单词没有什么区别。
    • 代替resource.getInpuStream(),也可以使用resource.getURL()
    【解决方案2】:

    我知道这个问题已经得到解答。但是,对于那些使用 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()。它现在对我有用!

    谢谢!

    【讨论】:

      【解决方案3】:

      在spring jar包中,我使用newClassPathResource(filename).getFile(),会抛出异常:

      无法解析为绝对文件路径,因为它不驻留在文件系统中:jar

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

      【讨论】:

      • 这是正确的解决方案。谢谢
      【解决方案4】:

      错误消息是正确的(如果不是很有帮助):我们尝试加载的文件不是文件系统上的文件,而是 ZIP 中 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);
      

      【讨论】:

        【解决方案5】:

        我在使用 Tomcat6.x 时遇到了类似的问题,我发现的任何建议都没有帮助。 最后我删除了work 文件夹(Tomcat),问题就消失了。

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

        【讨论】:

          【解决方案6】:

          我在我的 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;
            }
          }
          

          【讨论】:

            【解决方案7】:

            在我看来,@sbk 的答案是我们应该在 spring-boot 环境中执行此操作(@Value("${classpath*:}) 除外)。但在我的场景中,如果从独立 jar 执行..可能是我做错了什么。

            但这可能是另一种方式,

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

            【讨论】:

              【解决方案8】:

              我遇到了一个更复杂的问题,因为我有多个同名文件,一个位于主 Spring Boot jar 中,其他位于主 fat jar 中的 jar 中。 我的解决方案是获取所有具有相同名称的资源,然后获取我需要按包名称过滤的资源。 获取所有文件:

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

              【讨论】:

                【解决方案9】:

                对于 kotlin 用户,我是这样解决的:

                val url = ResourceUtils.getURL("classpath:$fileName")
                val response = url.openStream().bufferedReader().readText()
                

                【讨论】:

                  【解决方案10】:

                  我遇到了同样的问题,最终使用了更方便的Guava Resources

                  Resources.getResource("my.file")
                  

                  【讨论】:

                    【解决方案11】:

                    虽然这是一个非常古老的线程,但我在 Spring Boot 应用程序中添加 FCM 时也遇到了同样的问题。

                    在开发中,文件被打开并且没有错误,但是当我将应用程序部署到 AWS Elastic beanstalk 时,FileNotFoundException 的错误被抛出并且 FCM 无法正常工作。

                    所以这是我的解决方案,可以让它同时用于开发环境和 jar 部署生产。

                    我有一个组件类 FCMService,它的方法如下:

                      @PostConstruct
                      public void initialize() {
                    
                        log.info("Starting FCM Service");
                        InputStream inputStream;
                    
                        try {
                    
                          ClassPathResource resource = new ClassPathResource("classpath:fcm/my_project_firebase_config.json");
                          URL url = null;
                          try {
                            url = resource.getURL();
                          } catch (IOException e) {
                    
                          }
                    
                          if (url != null) {
                            inputStream = url.openStream();
                          } else {
                            File file = ResourceUtils.getFile("classpath:fcm/my_project_firebase_config.json");
                            inputStream = new FileInputStream(file);
                          }
                    
                          FirebaseOptions options = FirebaseOptions.builder().setCredentials(GoogleCredentials.fromStream(inputStream))
                              .build();
                          FirebaseApp.initializeApp(options);
                    
                          log.info("FCM Service started");
                    
                        } catch (IOException e) {
                          log.error("Error starting FCM Service");
                          e.printStackTrace();
                        }
                    
                      }
                    

                    希望这有助于寻求快速解决实施 FCM 的人。

                    【讨论】:

                      【解决方案12】:

                      在 Spring boot 1.5.22.RELEASE Jar 包装中,这对我有用:

                      InputStream resource = new ClassPathResource("example.pdf").getInputStream();
                                  
                      

                      “example.pdf”在 src/main/resources 中。

                      然后将其读取为 byte[]

                      FileCopyUtils.copyToByteArray(resource);
                      

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 2020-02-24
                        • 2020-09-21
                        • 1970-01-01
                        • 1970-01-01
                        • 2015-02-25
                        • 2013-08-02
                        • 1970-01-01
                        • 2020-06-17
                        相关资源
                        最近更新 更多