Ressource de chemin de classe introuvable lors de l'exécution en tant que fichier jar

128

Ayant ce problème à la fois dans Spring Boot 1.1.5 et 1.1.6 - Je charge une ressource de chemin de classe à l'aide d'une annotation @Value, qui fonctionne très bien lorsque j'exécute l'application à partir de STS (3.6.0, Windows). Cependant, lorsque j'exécute un package mvn puis que j'essaye d'exécuter le fichier jar, j'obtiens des exceptions FileNotFound.

La ressource, message.txt, se trouve dans src / main / resources. J'ai inspecté le pot et vérifié qu'il contient le fichier "message.txt" au niveau supérieur (même niveau que application.properties).

Voici l'application:

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

L'éxéption:

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
la source

Réponses:

201

resource.getFile()s'attend à ce que la ressource elle-même soit disponible sur le système de fichiers, c'est-à-dire qu'elle ne peut pas être imbriquée dans un fichier jar. C'est pourquoi cela fonctionne lorsque vous exécutez votre application dans STS, mais ne fonctionne pas une fois que vous avez créé votre application et l'exécutez à partir du fichier jar exécutable. Plutôt que d'utiliser getFile()pour accéder au contenu de la ressource, je vous recommande d'utiliser à la getInputStream()place. Cela vous permettra de lire le contenu de la ressource quel que soit son emplacement.

Andy Wilkinson
la source
6
Dans mon cas, je dois fournir un chemin vers le fichier keystore (pour le bien d'un connecteur https tomcat) .Je veux envelopper le fichier jks dans mon jar. Selon la solution ci-dessus, ce n'est pas possible. Connaissez-vous un moyen supplémentaire de le faire?
Modi
1
J'ai une exigence très similaire et il n'y a pas d'API qui vous permettent de définir le keystore dans le cadre du fichier jar. @Modi, avez-vous trouvé une solution?
Robin Varghese
Voulez-vous dire pour le cas d'utilisation du magasin de clés? Utilisez-vous Spring Boot avec Tomcat?
Modi
@RobinVarghese avez-vous trouvé une solution à votre problème. J'ai un cas d'utilisation similaire à résoudre. Merci si vous avez des suggestions.
horizon7
Dans le cas où tomcat a besoin de l'emplacement du keystore pour activer https, on peut l'utiliser classpath:filenamepour que le fichier de keystore puisse être lu à partir du fichier jar.
horizon7
60

Si vous utilisez le framework Spring, lire ClassPathResourcedans un Stringest assez simple en utilisant le framework SpringFileCopyUtils :

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
la source
Y a-t-il une contrainte quant à l'emplacement du fichier? Cela peut-il être n'importe où dans le chemin des classes? Peut-il être à l'origine du projet?
Stephane
Cela peut être n'importe où dans classpath.
anubhava
45

Si vous souhaitez utiliser un fichier:

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
la source
7

lorsque le projet de démarrage de printemps s'exécute en tant que jar et que vous avez besoin de lire un fichier dans le chemin de classe, je l'implémente par le code ci-dessous

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

J'ai créé une classe ClassPathResourceReader de manière java 8 pour faciliter la lecture des fichiers à partir du chemin de classe

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

Utilisation:

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

J'ai également rencontré cette limitation et j'ai créé cette bibliothèque pour résoudre le problème: spring-boot-jar-resources Cela vous permet essentiellement d'enregistrer un ResourceLoader personnalisé avec Spring Boot qui extrait les ressources de chemin de classe du JAR au besoin, de manière transparente.

Ulises
la source
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
la source
1

Jersey doit être des bocaux déballés.

<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
la source
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
la source
0

Sur la base de la réponse d'Andy, j'ai utilisé ce qui suit pour obtenir un flux d'entrée de tous les YAML sous un répertoire et des sous-répertoires dans les ressources (notez que le chemin passé ne commence pas par /):

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
la source
0

Je suis également coincé dans une situation similaire mais pas exactement similaire, je voulais lire un fichier JSON à partir du dossier de ressources.src / main / resources Par conséquent, j'ai écrit un code comme celui-ci ci-dessous.

Il existe plusieurs méthodes répertoriées ici pour lire un fichier de chemin de classe dans Spring Boot Application.

@Value ("classpath: test.json") ressource de ressource privée;

File file = resource.getFile();

@Value("classpath:test.json")

ressource de ressource privée;

File file = resource.getFile();

Maintenant, ce code fonctionne parfaitement une fois que je l'ai exécuté en utilisant, mvn: spring-boot: run, mais dès que je construis et empaquette l'application en utilisant maven et que je l'exécute comme un simple fichier exécutable, j'obtiens une exception. Allons-y et trouvons la solution maintenant.

En utilisant InputStream, j'ai trouvé la réponse dans mon cas: -

@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 fonctionne sur le concept de Fat Jar, il est donc vraiment difficile car il se comporte différemment dans Eclipse et pendant que vous exécutez comme un pot.

Sanjay-Dev
la source
0

Concernant le message d'erreur d'origine

ne peut pas être résolu en chemin de fichier absolu car il ne réside pas dans le système de fichiers

Le code suivant peut être utile pour trouver la solution au problème de chemin:

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

Avec cela, vous pouvez déterminer où l'application attend le fichier manquant. Vous pouvez l'exécuter dans la méthode principale de votre application.

SJX
la source