Je voudrais lire une ressource de mon pot comme ceci:
File file;
file = new File(getClass().getResource("/file.txt").toURI());
BufferredReader reader = new BufferedReader(new FileReader(file));
//Read the file
et cela fonctionne bien quand on l'exécute dans Eclipse, mais si je l'exporte dans un fichier jar, il y a une exception IllegalArgumentException:
Exception in thread "Thread-2"
Java.lang.IllegalArgumentException: URI is not hierarchical
et je ne sais vraiment pas pourquoi mais avec quelques tests j'ai trouvé si je change
file = new File(getClass().getResource("/file.txt").toURI());
à
file = new File(getClass().getResource("/folder/file.txt").toURI());
alors ça marche le contraire (ça marche en pot mais pas en Eclipse).
J'utilise Eclipse et le dossier contenant mon fichier se trouve dans un dossier de classe.
Plutôt que d'essayer de traiter la ressource en tant que Fichier , demandez simplement à ClassLoader de renvoyer un InputStream pour la ressource via getResourceAsStream :
InputStream in = getClass().getResourceAsStream("/file.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
Tant que la ressource file.txt
est disponible sur le chemin de classe, cette approche fonctionnera de la même manière, que la ressource file.txt
se trouve dans un répertoire classes/
ou à l'intérieur d'un jar
.
Le URI is not hierarchical
se produit car l'URI d'une ressource dans un fichier JAR va ressembler à ceci: file:/example.jar!/file.txt
. Vous ne pouvez pas lire les entrées d'un fichier jar
(un fichier Zip
) comme s'il s'agissait d'un ancien vieux Fichier .
Ceci s’explique bien par les réponses à:
Pour accéder à un fichier dans un fichier jar, vous avez deux options:
Placez le fichier dans la structure de répertoires correspondant au nom de votre package (après avoir extrait le fichier .jar, il doit se trouver dans le même répertoire que le fichier .class), puis accédez-y à l'aide de getClass().getResourceAsStream("file.txt")
.
Placez le fichier à la racine (après avoir extrait le fichier .jar, il devrait être à la racine), puis accédez-y à l'aide de Thread.currentThread().getContextClassLoader().getResourceAsStream("file.txt")
La première option peut ne pas fonctionner lorsque jar est utilisé comme plugin.
Si vous voulez lire comme un fichier, je crois qu'il existe toujours une solution similaire:
ClassLoader classLoader = getClass().getClassLoader();
File file = new File(classLoader.getResource("file/test.xml").getFile());
J'ai eu ce problème avant et j'ai fait de manière de secours pour le chargement. Fondamentalement, la première méthode fonctionne dans un fichier .jar et la deuxième méthode dans Eclipse ou un autre IDE.
public class MyClass {
public static InputStream accessFile() {
String resource = "my-file-located-in-resources.txt";
// this is the path within the jar file
InputStream input = MyClass.class.getResourceAsStream("/resources/" + resource);
if (input == null) {
// this is how we load file within editor (eg Eclipse)
input = MyClass.class.getClassLoader().getResourceAsStream(resource);
}
return input;
}
}
Assurez-vous de travailler avec le bon séparateur. J'ai remplacé tous les /
d'un chemin relatif par un File.separator
. Cela a bien fonctionné dans l'IDE, mais n'a pas fonctionné dans le fichier JAR de génération.
Jusqu'à présent (décembre 2017), c'est la seule solution que j'ai trouvée qui fonctionne les deux à l'intérieur et à l'extérieur de l'IDE.
Utilisez PathMatchingResourcePatternResolver
Note: cela fonctionne aussi dans spring-boot
Dans cet exemple, je lis des fichiers situés dans src/main/resources/my_folder:
try {
// Get all the files under this inner resource folder: my_folder
String scannedPackage = "my_folder/*";
PathMatchingResourcePatternResolver scanner = new PathMatchingResourcePatternResolver();
Resource[] resources = scanner.getResources(scannedPackage);
if (resources == null || resources.length == 0)
log.warn("Warning: could not find any resources in this scanned package: " + scannedPackage);
else {
for (Resource resource : resources) {
log.info(resource.getFilename());
// Read the file content (I used BufferedReader, but there are other solutions for that):
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
String line = null;
while ((line = bufferedReader.readLine()) != null) {
// ...
// ...
}
bufferedReader.close();
}
}
} catch (Exception e) {
throw new Exception("Failed to read the resources folder: " + e.getMessage(), e);
}
Après de nombreuses recherches en Java, la seule solution qui semble fonctionner pour moi est de lire manuellement le fichier jar lui-même, sauf si vous vous trouvez dans un environnement de développement (IDE):
/** @return The root folder or jar file that the class loader loaded from */
public static final File getClasspathFile() {
return new File(YourMainClass.class.getProtectionDomain().getCodeSource().getLocation().getFile());
}
/** @param resource The path to the resource
* @return An InputStream containing the resource's contents, or
* <b><code>null</code></b> if the resource does not exist */
public static final InputStream getResourceAsStream(String resource) {
resource = resource.startsWith("/") ? resource : "/" + resource;
if(getClasspathFile().isDirectory()) {//Development environment:
return YourMainClass.class.getResourceAsStream(resource);
}
final String res = resource;//Jar or exe:
return AccessController.doPrivileged(new PrivilegedAction<InputStream>() {
@SuppressWarnings("resource")
@Override
public InputStream run() {
try {
final JarFile jar = new JarFile(getClasspathFile());
String resource = res.startsWith("/") ? res.substring(1) : res;
if(resource.endsWith("/")) {//Directory; list direct contents:(Mimics normal getResourceAsStream("someFolder/") behaviour)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Enumeration<JarEntry> entries = jar.entries();
while(entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if(entry.getName().startsWith(resource) && entry.getName().length() > resource.length()) {
String name = entry.getName().substring(resource.length());
if(name.contains("/") ? (name.endsWith("/") && (name.indexOf("/") == name.lastIndexOf("/"))) : true) {//If it's a folder, we don't want the children's folders, only the parent folder's children!
name = name.endsWith("/") ? name.substring(0, name.length() - 1) : name;
baos.write(name.getBytes(StandardCharsets.UTF_8));
baos.write('\r');
baos.write('\n');
}
}
}
jar.close();
return new ByteArrayInputStream(baos.toByteArray());
}
JarEntry entry = jar.getJarEntry(resource);
InputStream in = entry != null ? jar.getInputStream(entry) : null;
if(in == null) {
jar.close();
return in;
}
final InputStream stream = in;//Don't manage 'jar' with try-with-resources or close jar until the
return new InputStream() {//returned stream is closed(closing the jar closes all associated InputStreams):
@Override
public int read() throws IOException {
return stream.read();
}
@Override
public int read(byte b[]) throws IOException {
return stream.read(b);
}
@Override
public int read(byte b[], int off, int len) throws IOException {
return stream.read(b, off, len);
}
@Override
public long skip(long n) throws IOException {
return stream.skip(n);
}
@Override
public int available() throws IOException {
return stream.available();
}
@Override
public void close() throws IOException {
try {
jar.close();
} catch(IOException ignored) {
}
stream.close();
}
@Override
public synchronized void mark(int readlimit) {
stream.mark(readlimit);
}
@Override
public synchronized void reset() throws IOException {
stream.reset();
}
@Override
public boolean markSupported() {
return stream.markSupported();
}
};
} catch(Throwable e) {
e.printStackTrace();
return null;
}
}
});
}
Note: Le code ci-dessus ne semble fonctionner correctement pour les fichiers jar que s'il est dans la classe principale. Je ne sais pas pourquoi.
Le code ci-dessous fonctionne avec Spring Boot (kotlin):
val authReader = InputStreamReader(javaClass.getResourceAsStream("/file1.json"))
Si vous utilisez spring, vous pouvez utiliser la méthode suivante pour lire le fichier depuis src/main/resources:
import Java.io.BufferedReader;
import Java.io.IOException;
import Java.io.InputStream;
import Java.io.InputStreamReader;
import org.springframework.core.io.ClassPathResource;
public String readFileToString(String path) throws IOException {
StringBuilder resultBuilder = new StringBuilder("");
ClassPathResource resource = new ClassPathResource(path);
try (
InputStream inputStream = resource.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
resultBuilder.append(line);
}
}
return resultBuilder.toString();
}
Je pense que cela devrait également fonctionner dans Java. Le code suivant que j'utilise utilise kotlin.
val resource = Thread.currentThread().contextClassLoader.getResource('resources.txt')
Vous pouvez utiliser le chargeur de classe qui lit le classpath en tant que chemin ROOT (sans "/" au début)
InputStream in = getClass().getClassLoader().getResourceAsStream("file.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
Le problème est que certaines bibliothèques tierces nécessitent des noms de chemin de fichiers plutôt que des flux d'entrée. La plupart des réponses n'abordent pas ce problème.
Dans ce cas, une solution de contournement consiste à copier le contenu de la ressource dans un fichier temporaire. L'exemple suivant utilise TemporaryFolder
de jUnit.
private List<String> decomposePath(String path){
List<String> reversed = Lists.newArrayList();
File currFile = new File(path);
while(currFile != null){
reversed.add(currFile.getName());
currFile = currFile.getParentFile();
}
return Lists.reverse(reversed);
}
private String writeResourceToFile(String resourceName) throws IOException {
ClassLoader loader = getClass().getClassLoader();
InputStream configStream = loader.getResourceAsStream(resourceName);
List<String> pathComponents = decomposePath(resourceName);
folder.newFolder(pathComponents.subList(0, pathComponents.size() - 1).toArray(new String[0]));
File tmpFile = folder.newFile(resourceName);
Files.copy(configStream, tmpFile.toPath(), REPLACE_EXISTING);
return tmpFile.getAbsolutePath();
}
Pour une raison quelconque, classLoader.getResource()
renvoyait toujours la valeur null lorsque j'ai déployé l'application Web dans WildFly 14. L'obtention de classLoader à partir de getClass().getClassLoader()
ou Thread.currentThread().getContextClassLoader()
renvoie null.
getClass().getClassLoader()
doc API dit,
"Retourne le chargeur de classe pour la classe. Certaines implémentations peuvent utiliser null pour représenter le chargeur de classe bootstrap. Cette méthode renvoie null dans ces implémentations si cette classe a été chargée par la classe bootstrap chargeur."
peut-être si vous utilisez WildFly et votre application Web, essayez ceci
request.getServletContext().getResource()
a renvoyé l'URL de la ressource. Ici, la demande est un objet de ServletRequest.