Klassenpfadressource nicht gefunden, wenn als jar ausgeführt wird

127

Dieses Problem tritt sowohl in Spring Boot 1.1.5 als auch in 1.1.6 auf. Ich lade eine Klassenpfadressource mithilfe einer @ Value-Annotation. Dies funktioniert einwandfrei, wenn ich die Anwendung in STS (3.6.0, Windows) ausführe. Wenn ich jedoch ein MVN-Paket ausführe und dann versuche, das JAR auszuführen, erhalte ich FileNotFound-Ausnahmen.

Die Ressource message.txt befindet sich in src / main / resources. Ich habe das JAR überprüft und überprüft, dass es die Datei "message.txt" auf der obersten Ebene enthält (dieselbe Ebene wie application.properties).

Hier ist die Anwendung:

@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());
        }
    }
}

Die Ausnahme:

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
Gyoder
quelle

Antworten:

201

resource.getFile()erwartet, dass die Ressource selbst im Dateisystem verfügbar ist, dh nicht in einer JAR-Datei verschachtelt werden kann. Aus diesem Grund funktioniert es, wenn Sie Ihre Anwendung in STS ausführen, aber nicht, wenn Sie Ihre Anwendung erstellt und über die ausführbare JAR-Datei ausgeführt haben. Anstatt zu verwenden, getFile()um auf den Inhalt der Ressource zuzugreifen, würde ich empfehlen, getInputStream()stattdessen zu verwenden. Auf diese Weise können Sie den Inhalt der Ressource unabhängig von ihrem Standort lesen.

Andy Wilkinson
quelle
6
In meinem Fall muss ich einen Pfad zur Keystore-Datei angeben (für einen Tomcat-https-Connector). Ich möchte die JKS-Datei in mein JAR einbinden. Nach der obigen Lösung ist dies nicht möglich. Kennen Sie zusätzliche Möglichkeiten?
Modi
1
Ich habe eine sehr ähnliche Anforderung und es gibt keine APIs, mit denen Sie den Keystore als Teil des Glases einstellen können. @Modi, konnten Sie eine Lösung finden?
Robin Varghese
Meinen Sie für den Key Store-Anwendungsfall? Verwenden Sie Spring Boot mit Tomcat?
Modi
@RobinVarghese haben Sie eine Lösung für Ihr Problem gefunden. Ich habe einen ähnlichen Anwendungsfall zu lösen. Schätzen Sie, wenn Sie einen Vorschlag haben.
Horizont7
Falls Tomcat einen Keystore-Speicherort benötigt, um https zu aktivieren, kann dieser verwendet werden, classpath:filenamedamit die Keystore-Datei aus dem JAR gelesen werden kann.
Horizont7
60

Wenn Sie Spring Framework verwenden, ist das Einlesen ClassPathResourcein ein FrameworkString mit Spring FrameworksFileCopyUtils ziemlich einfach :

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);
}
Anubhava
quelle
Gibt es eine Einschränkung, wo sich die Datei befinden muss? Kann es irgendwo im Klassenpfad sein? Kann es die Wurzel des Projekts sein?
Stephane
Es kann überall im Klassenpfad sein.
Anubhava
45

Wenn Sie eine Datei verwenden möchten:

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);
}
Jae-il Lee
quelle
7

Wenn das Spring-Boot-Projekt als JAR ausgeführt wird und eine Datei im Klassenpfad gelesen werden muss, implementiere ich sie mit dem folgenden Code

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

Ich habe eine ClassPathResourceReader-Klasse auf Java 8-Weise erstellt, um das Lesen von Dateien aus dem Klassenpfad zu vereinfachen

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;
    }
}

Nutzung:

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

Ich bin auch auf diese Einschränkung gestoßen und habe diese Bibliothek erstellt, um das Problem zu beheben: spring-boot-jar-resources Grundsätzlich können Sie einen benutzerdefinierten ResourceLoader bei Spring Boot registrieren, der die Klassenpfadressourcen nach Bedarf transparent aus der JAR extrahiert.

Ulises
quelle
2
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);
        }
}
Jayshree Pancholi
quelle
1

Jersey muss ausgepackt werden.

<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>  
utkusonmez
quelle
0
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"))**
Merzouk MENHOUR
quelle
0

Basierend auf Andys Antwort habe ich Folgendes verwendet, um Eingabestreams aller YAMLs unter einem Verzeichnis und Unterverzeichnissen in Ressourcen abzurufen (Beachten Sie, dass der übergebene Pfad nicht mit beginnt /):

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();
    }
}
aksh1618
quelle
0

Ich steckte auch in einer ähnlichen Situation fest, aber nicht genau ähnlich, wollte eine JSON-Datei aus dem Ressourcenordner lesen. SRC / main / resources Daher schrieb ich einen Code wie diesen unten.

Hier sind verschiedene Möglichkeiten aufgeführt, um eine Datei zu lesen. F. Klassenpfad in Spring Boot Application .

@Value ("Klassenpfad: test.json") private Ressourcenressource;

File file = resource.getFile();

@Value("classpath:test.json")

private Ressourcenressource;

File file = resource.getFile();

Jetzt funktioniert dieser Code einwandfrei, wenn ich ihn mit mvn: spring-boot: run ausführe. Sobald ich jedoch die Anwendung mit maven erstelle und packe und sie als einfache ausführbare JAR-Datei ausführe, erhalte ich eine Ausnahme. Lassen Sie uns jetzt die Lösung herausfinden.

Mit InputStream habe ich in meinem Fall die Antwort gefunden: -

@Value("classpath:test.json")
    Resource resourceFile;

private void testResource(Resource resource) {
        try {
            InputStream resourcee = resource.getInputStream();
            String text = null;
            try (final Reader reader = new InputStreamReader(resourcee)) {
                text = CharStreams.toString(reader);
            }
            System.out.println(text);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

Spring arbeitet am Konzept des Fat Jar, daher ist es sehr schwierig, da es sich in Eclipse anders verhält und während Sie als Jar laufen.

Sanjay-Dev
quelle