Recurso de classpath no encontrado cuando se ejecuta como jar

128

Al tener este problema tanto en Spring Boot 1.1.5 como en 1.1.6: estoy cargando un recurso classpath usando una anotación @Value, que funciona bien cuando ejecuto la aplicación desde STS (3.6.0, Windows). Sin embargo, cuando ejecuto un paquete mvn y luego intento ejecutar el jar, obtengo excepciones FileNotFound.

El recurso, message.txt, está en src / main / resources. Inspeccioné el jar y verifiqué que contiene el archivo "message.txt" en el nivel superior (el mismo nivel que application.properties).

Aquí está la aplicación:

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

La excepción:

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
fuente

Respuestas:

201

resource.getFile()espera que el recurso en sí esté disponible en el sistema de archivos, es decir, no se puede anidar dentro de un archivo jar. Es por eso que funciona cuando ejecuta su aplicación en STS, pero no funciona una vez que ha creado su aplicación y la ejecuta desde el archivo ejecutable. En lugar de usar getFile()para acceder a los contenidos del recurso, recomendaría usar en su getInputStream()lugar. Eso le permitirá leer el contenido del recurso independientemente de dónde se encuentre.

Andy Wilkinson
fuente
66
En mi caso, necesito proporcionar una ruta al archivo del almacén de claves (por el bien de un conector https tomcat). Quiero envolver el archivo jks dentro de mi jar. De acuerdo con la solución anterior, no es posible. ¿Estás familiarizado con una forma adicional de hacerlo?
Modi
1
Tengo un requisito muy similar y no hay apis que le permitan configurar el almacén de claves como parte del jar. @Modi, ¿pudiste encontrar alguna solución?
Robin Varghese
¿Te refieres al caso de uso de la tienda de claves? ¿Estás usando Spring Boot con Tomcat?
Modi
@RobinVarghese encontró alguna solución a su problema. Tengo un caso de uso similar para resolver. Agradezco si tienes alguna sugerencia.
horizon7
En caso de que Tomcat necesite la ubicación del almacén de claves para habilitar https, se puede usar classpath:filenamepara que el archivo del almacén de claves se pueda leer desde el jar.
horizon7
60

Si está usando Spring Framework, leer ClassPathResourceen un Stringes bastante simple usando Spring FrameworkFileCopyUtils :

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
fuente
¿Existe alguna restricción sobre dónde debe ubicarse el archivo? ¿Puede estar en cualquier lugar del classpath? ¿Puede estar en la raíz del proyecto?
Stephane
Puede estar en cualquier lugar de classpath.
anubhava
45

Si quieres usar un archivo:

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
fuente
7

cuando el proyecto de arranque de primavera se ejecuta como un jar y necesita leer algún archivo en classpath, lo implemento por debajo del código

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

He creado una clase ClassPathResourceReader de una manera java 8 para hacer que los archivos de classpath sean fáciles de leer

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

Utilización:

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

También encontré esta limitación y creé esta biblioteca para superar el problema: spring-boot-jar-resources Básicamente, le permite registrar un ResourceLoader personalizado con Spring Boot que extrae los recursos de classpath del JAR según sea necesario, de forma transparente.

Ulises
fuente
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
fuente
1

Jersey necesita ser frascos desempaquetados.

<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
fuente
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
fuente
0

Según la respuesta de Andy, utilicé lo siguiente para obtener una secuencia de entrada de todos los YAML en un directorio y subdirectorios en recursos (tenga en cuenta que la ruta pasada no comienza /):

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
fuente
0

También me encontré con una situación similar pero no exactamente similar, quería leer un archivo JSON de la carpeta de recursos.src / main / resources Por lo tanto, escribí un código como este a continuación.

Hay varias formas enumeradas aquí para leer un archivo f classpath de rom en Spring Boot Application.

@Value ("classpath: test.json") recurso de recurso privado;

File file = resource.getFile();

@Value("classpath:test.json")

recurso de recursos privados;

File file = resource.getFile();

Ahora, este código funciona completamente bien una vez que lo ejecuto usando, mvn: spring-boot: run, pero tan pronto como estoy compilando y empaquetando la aplicación usando maven y ejecutándola como un simple jar ejecutable, obtengo una excepción. Avancemos y descubramos la solución ahora.

Usar InputStream es lo que encontré la respuesta en mi caso: -

@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 trabaja en el concepto de Fat Jar, por lo tanto, es realmente difícil ya que se comporta de manera diferente en Eclipse y mientras corres como un frasco.

Sanjay-Dev
fuente
0

En cuanto al mensaje de error original

no se puede resolver en la ruta absoluta del archivo porque no reside en el sistema de archivos

El siguiente código podría ser útil para encontrar la solución al problema de ruta:

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

Con esto puede determinar dónde espera la aplicación el archivo que falta. Puede ejecutar esto en el método principal de su aplicación.

SJX
fuente