【问题标题】:Classpath resource not found when running as jar作为 jar 运行时找不到类路径资源
【发布时间】:2014-11-10 05:48:11
【问题描述】:

在 Spring Boot 1.1.5 和 1.1.6 中都有这个问题 - 我正在使用 @Value 注释加载类路径资源,当我从 STS(3.6.0,Windows)中运行应用程序时,它工作得很好.但是,当我运行 mvn 包然后尝试运行 jar 时,我得到 FileNotFound 异常。

资源 message.txt 位于 src/main/resources 中。我检查了 jar 并验证它在顶层(与 application.properties 相同的级别)包含文件“message.txt”。

这是应用程序:

@Configuration
@ComponentScan
@EnableAutoConfiguration
public class Application implements CommandLineRunner {

    private static final Logger logger = Logger.getLogger(Application.class);

    @Value("${message.file}")
    private Resource messageResource;

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(String... arg0) throws Exception {
        // both of these work when running as Spring boot app from STS, but
        // fail after mvn package, and then running as java -jar
        testResource(new ClassPathResource("message.txt"));
        testResource(this.messageResource);
    }

    private void testResource(Resource resource) {
        try {
            resource.getFile();
            logger.debug("Found the resource " + resource.getFilename());
        } catch (IOException ex) {
            logger.error(ex.toString());
        }
    }
}

例外:

c:\Users\glyoder\Documents\workspace-sts-3.5.1.RELEASE\classpath-resource-proble
m\target>java -jar demo-0.0.1-SNAPSHOT.jar

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.1.5.RELEASE)

2014-09-16 08:46:34.635  INFO 5976 --- [           main] demo.Application
                  : Starting Application on 8W59XV1 with PID 5976 (C:\Users\glyo
der\Documents\workspace-sts-3.5.1.RELEASE\classpath-resource-problem\target\demo
-0.0.1-SNAPSHOT.jar started by glyoder in c:\Users\glyoder\Documents\workspace-s
ts-3.5.1.RELEASE\classpath-resource-problem\target)
2014-09-16 08:46:34.640 DEBUG 5976 --- [           main] demo.Application
                  : Running with Spring Boot v1.1.5.RELEASE, Spring v4.0.6.RELEA
SE
2014-09-16 08:46:34.681  INFO 5976 --- [           main] s.c.a.AnnotationConfigA
pplicationContext : Refreshing org.springframework.context.annotation.Annotation
ConfigApplicationContext@1c77b086: startup date [Tue Sep 16 08:46:34 EDT 2014];
root of context hierarchy
2014-09-16 08:46:35.196  INFO 5976 --- [           main] o.s.j.e.a.AnnotationMBe
anExporter        : Registering beans for JMX exposure on startup
2014-09-16 08:46:35.210 ERROR 5976 --- [           main] demo.Application
                  : java.io.FileNotFoundException: class path resource [message.
txt] cannot be resolved to absolute file path because it does not reside in the
file system: jar:file:/C:/Users/glyoder/Documents/workspace-sts-3.5.1.RELEASE/cl
asspath-resource-problem/target/demo-0.0.1-SNAPSHOT.jar!/message.txt
2014-09-16 08:46:35.211 ERROR 5976 --- [           main] demo.Application
                  : java.io.FileNotFoundException: class path resource [message.
txt] cannot be resolved to absolute file path because it does not reside in the
file system: jar:file:/C:/Users/glyoder/Documents/workspace-sts-3.5.1.RELEASE/cl
asspath-resource-problem/target/demo-0.0.1-SNAPSHOT.jar!/message.txt
2014-09-16 08:46:35.215  INFO 5976 --- [           main] demo.Application
                  : Started Application in 0.965 seconds (JVM running for 1.435)

2014-09-16 08:46:35.217  INFO 5976 --- [       Thread-2] s.c.a.AnnotationConfigA
pplicationContext : Closing org.springframework.context.annotation.AnnotationCon
figApplicationContext@1c77b086: startup date [Tue Sep 16 08:46:34 EDT 2014]; roo
t of context hierarchy
2014-09-16 08:46:35.218  INFO 5976 --- [       Thread-2] o.s.j.e.a.AnnotationMBe
anExporter        : Unregistering JMX-exposed beans on shutdown

【问题讨论】:

    标签: java spring-boot


    【解决方案1】:

    resource.getFile() 期望资源本身在文件系统上可用,即它不能嵌套在 jar 文件中。这就是为什么当您在 STS(Spring Tool Suite)中运行您的应用程序时它可以工作,但一旦您构建了应用程序并从可执行 jar 运行它就不能工作。我建议不要使用getFile() 来访问资源的内容,而是使用getInputStream()。这将允许您阅读资源的内容,无论它位于何处。

    【讨论】:

    • 在我的情况下,我需要提供密钥库文件的路径(为了 tomcat https 连接器)。我想将 jks 文件包装在我的 jar 中。根据上述解决方案,这是不可能的。您是否熟悉其他方法?
    • 我有一个非常相似的要求,并且没有 API 可以让您将密钥库设置为 jar 的一部分。 @Modi,您找到任何解决方案了吗?
    • 您的意思是密钥存储用例吗?你在使用 Spring Boot 和 Tomcat 吗?
    • @RobinVarghese 你找到解决问题的方法了吗?我有一个类似的用例要解决。如果您有什么建议,不胜感激。
    • 如果tomcat需要keystore位置来启用https,可以使用classpath:filename以便可以从jar中读取keystore文件。
    【解决方案2】:

    如果您使用的是 Spring 框架,那么使用 Spring framework's FileCopyUtilsClassPathResource 读入 String 非常简单:

    String data = "";
    ClassPathResource cpr = new ClassPathResource("static/file.txt");
    try {
        byte[] bdata = FileCopyUtils.copyToByteArray(cpr.getInputStream());
        data = new String(bdata, StandardCharsets.UTF_8);
    } catch (IOException e) {
        LOG.warn("IOException", e);
    }
    

    【讨论】:

    • 文件必须放在哪里有限制吗?它可以在类路径中的任何地方吗?可以在项目的根目录下吗?
    • 它可以在类路径中的任何位置。
    • 不适用于 jar,从 IDE 可以获取数据
    【解决方案3】:

    如果要使用文件:

    ClassPathResource classPathResource = new ClassPathResource("static/something.txt");
    
    InputStream inputStream = classPathResource.getInputStream();
    File somethingFile = File.createTempFile("test", ".txt");
    try {
        FileUtils.copyInputStreamToFile(inputStream, somethingFile);
    } finally {
        IOUtils.closeQuietly(inputStream);
    }
    

    【讨论】:

    • 适合任何需要的人。如果 FileUtils.copyInputStreamToFile 不可解析。您可能需要添加“commons-io”作为依赖项。
    【解决方案4】:

    当spring boot项目作为jar运行并且需要读取classpath中的一些文件时,我通过下面的代码实现它

    Resource resource = new ClassPathResource("data.sql");
    BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
    reader.lines().forEach(System.out::println);
    

    【讨论】:

    • 如何读取键/值对,例如:敏感。属性
    【解决方案5】:

    我以 java 8 的方式创建了一个 ClassPathResourceReader 类,以便从类路径轻松读取文件

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.util.stream.Collectors;
    
    import org.springframework.core.io.ClassPathResource;
    
    public final class ClassPathResourceReader {
    
        private final String path;
    
        private String content;
    
        public ClassPathResourceReader(String path) {
            this.path = path;
        }
    
        public String getContent() {
            if (content == null) {
                try {
                    ClassPathResource resource = new ClassPathResource(path);
                    BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
                    content = reader.lines().collect(Collectors.joining("\n"));
                    reader.close();
                } catch (IOException ex) {
                    throw new RuntimeException(ex);
                }
            }
            return content;
        }
    }
    

    利用:

    String content = new ClassPathResourceReader("data.sql").getContent();
    

    【讨论】:

      【解决方案6】:
      to get list of data from src/main/resources/data folder --
      first of all mention your folder location in properties file as - 
      resourceLoader.file.location=data
      
      inside class declare your location. 
      
      @Value("${resourceLoader.file.location}")
          @Setter
          private String location;
      
          private final ResourceLoader resourceLoader;
      
      public void readallfilesfromresources() {
             Resource[] resources;
      
              try {
                  resources = ResourcePatternUtils.getResourcePatternResolver(resourceLoader).getResources("classpath:" + location + "/*.json");
                  for (int i = 0; i < resources.length; i++) {
                      try {
                      InputStream is = resources[i].getInputStream();
                      byte[] encoded = IOUtils.toByteArray(is);
                      String content = new String(encoded, Charset.forName("UTF-8"));
                      }
                  }
              } catch (IOException e) {
                  throw new UncheckedIOException(e);
              }
      }
      

      【讨论】:

        【解决方案7】:

        我也遇到了这个限制并创建了这个库来解决这个问题:spring-boot-jar-resources 它基本上允许您使用 Spring Boot 注册一个自定义 ResourceLoader,它可以根据需要透明地从 JAR 中提取类路径资源。

        【讨论】:

          【解决方案8】:

          球衣需要解压罐子。

          <build>  
              <plugins>
                  <plugin>
                      <groupId>org.springframework.boot</groupId>
                      <artifactId>spring-boot-maven-plugin</artifactId>
                      <configuration>
                          <requiresUnpack>
                              <dependency>
                                  <groupId>com.myapp</groupId>
                                  <artifactId>rest-api</artifactId>
                              </dependency>
                          </requiresUnpack>
                      </configuration>
                  </plugin>
              </plugins>
          </build>  
          

          【讨论】:

            【解决方案9】:
            in spring boot :
            
            1) if your file is ouside jar you can use :        
            
            @Autowired
            private ResourceLoader resourceLoader;
            
            **.resource(resourceLoader.getResource("file:/path_to_your_file"))**
            
            2) if your file is inside resources of jar you can `enter code here`use :
            
            **.resource(new ClassPathResource("file_name"))**
            

            【讨论】:

              【解决方案10】:

              基于 Andy 的answer,我使用以下方法获取了资源中一个目录和子目录下所有 YAML 的输入流(注意传递的路径不是以/ 开头):

              private static Stream<InputStream> getInputStreamsFromClasspath(
                      String path,
                      PathMatchingResourcePatternResolver resolver
              ) {
                  try {
                      return Arrays.stream(resolver.getResources("/" + path + "/**/*.yaml"))
                              .filter(Resource::exists)
                              .map(resource -> {
                                  try {
                                      return resource.getInputStream();
                                  } catch (IOException e) {
                                      return null;
                                  }
                              })
                              .filter(Objects::nonNull);
                  } catch (IOException e) {
                      logger.error("Failed to get definitions from directory {}", path, e);
                      return Stream.of();
                  }
              }
              

              【讨论】:

                【解决方案11】:

                关于原来的错误信息

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

                以下代码可能有助于找到路径问题的解决方案:

                Paths.get("message.txt").toAbsolutePath().toString();
                

                有了这个,您可以确定应用程序需要丢失文件的位置。您可以在应用程序的 main 方法中执行此操作。

                【讨论】:

                • 这不会有帮助,因为从 jar 存档中运行时,没有文件;它不在文件系统中。它作为资源在 jar 内。访问内容需要 InputStream。
                【解决方案12】:

                我注意到的另一件重要的事情是,在运行应用程序时,它会忽略资源文件夹中文件/文件夹中的大写字母,而在作为 jar 运行时它不会忽略它。因此,如果您的文件位于 Testfolder/messages.txt 下的资源文件夹中

                @Autowired
                ApplicationContext appContext;
                
                // this will work when running the application, but will fail when running as jar
                appContext.getResource("classpath:testfolder/message.txt");
                

                因此,不要在资源中使用大写字母,也不要在 ClassPathResource 的构造函数中添加这些大写字母:

                appContext.getResource("classpath:Testfolder/message.txt");
                

                【讨论】:

                • 忽略/不忽略大写字母必须是由于操作系统:运行应用程序时,您的 Windows 将忽略大写字母,而在运行 jar 时,Java 正在解析名称并正确尊重大写字母。
                【解决方案13】:

                我遇到了同样的错误

                InputStream inputStream = new ClassPathResource("filename.ext").inputStream();

                这应该在运行时解决 FileNotFoundException

                【讨论】:

                • 你好,这个回复是正确的,但基本上是重复以前的答案:6-year old5-year-old
                • 可能是这样,但我尝试了完全相同,错误得到了解决。
                【解决方案14】:

                这在静态环境中对我有用:

                    InputStream inputStream = ClassName.class.getClassLoader().getResourceAsStream("folderName/fileName.xml");
                    Reader reader = new InputStreamReader(inputStream);
                    String xml = CharStreams.toString(reader);
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2016-06-15
                  • 1970-01-01
                  • 1970-01-01
                  • 2010-12-26
                  • 2014-08-08
                  • 2014-10-20
                  相关资源
                  最近更新 更多