Ich möchte eine Ressource in meinem Glas folgendermaßen lesen:
File file;
file = new File(getClass().getResource("/file.txt").toURI());
BufferredReader reader = new BufferedReader(new FileReader(file));
//Read the file
und es funktioniert gut, wenn es in Eclipse ausgeführt wird, aber wenn ich es in ein jar exportiere, gibt es eine IllegalArgumentException:
Exception in thread "Thread-2"
Java.lang.IllegalArgumentException: URI is not hierarchical
und ich weiß wirklich nicht warum, aber mit ein paar Tests habe ich herausgefunden, ob ich mich ändere
file = new File(getClass().getResource("/file.txt").toURI());
zu
file = new File(getClass().getResource("/folder/file.txt").toURI());
dann funktioniert es im gegenteil (es funktioniert in jar aber nicht in eclipse).
Ich verwende Eclipse und der Ordner mit meiner Datei befindet sich in einem Klassenordner.
Anstatt zu versuchen, die Ressource als Datei anzusprechen , fragen Sie einfach den ClassLoader Um stattdessen einen InputStream für die Ressource über getResourceAsStream zurückzugeben:
InputStream in = getClass().getResourceAsStream("/file.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
Solange die Ressource file.txt
im Klassenpfad verfügbar ist, funktioniert dieser Ansatz auf dieselbe Weise, unabhängig davon, ob sich die Ressource file.txt
in einem Verzeichnis classes/
oder in einem Verzeichnis jar
befindet. .
Der URI is not hierarchical
tritt auf, weil der URI für eine Ressource in einer JAR-Datei ungefähr so aussieht: file:/example.jar!/file.txt
. Sie können die Einträge in einer jar
(einer Zip
-Datei) nicht lesen, als wäre es eine einfache alte Datei .
Dies wird durch die Antworten auf folgende Fragen gut erklärt:
Um auf eine Datei in einem Glas zuzugreifen, haben Sie zwei Möglichkeiten:
Platzieren Sie die Datei in der Verzeichnisstruktur, die Ihrem Paketnamen entspricht (nach dem Extrahieren der JAR-Datei sollte sie sich im selben Verzeichnis wie die CLASS-Datei befinden), und greifen Sie dann mit getClass().getResourceAsStream("file.txt")
darauf zu.
Platzieren Sie die Datei im Stammverzeichnis (nach dem Extrahieren der JAR-Datei sollte sie sich im Stammverzeichnis befinden) und greifen Sie dann mit Thread.currentThread().getContextClassLoader().getResourceAsStream("file.txt")
darauf zu.
Die erste Option funktioniert möglicherweise nicht, wenn jar als Plugin verwendet wird.
Wenn Sie als Datei lesen möchten, gibt es meines Erachtens noch eine ähnliche Lösung:
ClassLoader classLoader = getClass().getClassLoader();
File file = new File(classLoader.getResource("file/test.xml").getFile());
Ich hatte dieses Problem schon einmal und machte einen Fallback-Weg zum Laden. Grundsätzlich funktioniert der erste Weg innerhalb einer .jar-Datei und der zweite Weg innerhalb von Eclipse oder einer anderen 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;
}
}
Stellen Sie sicher, dass Sie mit dem richtigen Trennzeichen arbeiten. Ich habe alle /
in einem relativen Pfad durch File.separator
ersetzt. In der IDE hat dies problemlos funktioniert, in der Build-JAR jedoch nicht.
Bis jetzt (Dezember 2017) ist dies die einzige Lösung, die ich gefunden habe und die funktioniert beide innerhalb und außerhalb der IDE.
Verwenden Sie PathMatchingResourcePatternResolver
Anmerkung: funktioniert auch im Spring-Boot
In diesem Beispiel lese ich einige Dateien in 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);
}
Nach langem Herumstöbern in Java scheint die einzige Lösung für mich darin zu bestehen, die JAR-Datei selbst manuell zu lesen, es sei denn, Sie befinden sich in einer Entwicklungsumgebung (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;
}
}
});
}
Hinweis: Der obige Code scheint nur für JAR-Dateien korrekt zu funktionieren, wenn er zur Hauptklasse gehört. Ich bin mir nicht sicher warum.
Der folgende Code funktioniert mit Spring Boot (Kotlin):
val authReader = InputStreamReader(javaClass.getResourceAsStream("/file1.json"))
Wenn Sie spring verwenden, können Sie die Datei mit der folgenden Methode aus src/main/resources lesen:
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();
}
Ich denke, das sollte auch in Java funktionieren. Der folgende Code, den ich benutze, verwendet Kotlin.
val resource = Thread.currentThread().contextClassLoader.getResource('resources.txt')
Sie können Klassenladeprogramm verwenden, das vom Klassenpfad als ROOT-Pfad liest (ohne "/" am Anfang).
InputStream in = getClass().getClassLoader().getResourceAsStream("file.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
Das Problem ist, dass bestimmte Bibliotheken von Drittanbietern Dateipfadnamen anstelle von Eingabestreams benötigen. Die meisten Antworten behandeln dieses Problem nicht.
In diesem Fall besteht eine Problemumgehung darin, den Ressourceninhalt in eine temporäre Datei zu kopieren. Im folgenden Beispiel wird jUnits TemporaryFolder
verwendet.
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();
}
Aus irgendeinem Grund hat classLoader.getResource()
immer null zurückgegeben, wenn ich die Webanwendung für WildFly 14 bereitgestellt habe. Wenn classLoader von getClass().getClassLoader()
oder Thread.currentThread().getContextClassLoader()
abgerufen wurde, wird null zurückgegeben.
getClass().getClassLoader()
API doc sagt:
"Gibt das Klassenladeprogramm für die Klasse zurück. Einige Implementierungen verwenden möglicherweise null, um das Klassenladeprogramm bootstrap darzustellen. Diese Methode gibt in solchen Implementierungen null zurück, wenn diese Klasse von der Klasse bootstrap geladen wurde Lader. "
möglicherweise versuchen Sie dies, wenn Sie WildFly und Ihre Webanwendung verwenden
request.getServletContext().getResource()
hat die Ressourcen-URL zurückgegeben. Hier ist request ein Objekt von ServletRequest.