Come posso leggere un file di risorse da un file jar Java?

Sto cercando di accedere a un file XML all’interno di un file jar, da un jar separato in esecuzione come applicazione desktop. Posso ottenere l’URL del file di cui ho bisogno, ma quando lo passo a un FileReader (come una stringa) ottengo un FileNotFoundException che dice “Il nome del file, il nome della directory o la syntax dell’etichetta del volume non sono corretti.”

Come punto di riferimento, non ho problemi a leggere le risorse di immagini dallo stesso jar, passando l’URL a un costruttore ImageIcon. Questo sembra indicare che il metodo che sto usando per ottenere l’URL sia corretto.

URL url = getClass().getResource("/xxx/xxx/xxx/services.xml"); ServicesLoader jsl = new ServicesLoader( url.toString() ); 

All’interno della class ServicesLoader che ho

 XMLReader xr = XMLReaderFactory.createXMLReader(); xr.setContentHandler( this ); xr.setErrorHandler( this ); xr.parse( new InputSource( new FileReader( filename ))); 

Cosa c’è di sbagliato nell’usare questa tecnica per leggere il file XML?

Sembra che tu voglia usare java.lang.Class.getResourceAsStream(String) , vedi

http://java.sun.com/javase/6/docs/api/java/lang/Class.html#getResourceAsStream(java.lang.String)

Non dici se si tratta di un’applicazione desktop o web. Vorrei utilizzare il metodo getResourceAsStream() da un ClassLoader appropriato se si tratta di un desktop o del contesto se si tratta di un’app Web.

Sembra che tu stia utilizzando il risultato URL.toString come argomento del costruttore FileReader . URL.toString è un po ‘rotto, e in generale dovresti usare url.toURI().toString() . In ogni caso, la stringa non è un percorso di file.

Invece, dovresti:

  • Passa l’ URL a ServicesLoader e lascia che chiami openStream o simile.
  • Utilizza Class.getResourceAsStream e passa semplicemente lo stream sopra, possibilmente all’interno di un InputSource . (Ricorda di controllare i valori null in quanto l’API è un po ‘caotica).

Il problema era che stavo andando troppo oltre nel chiamare il metodo parse di XMLReader. Il metodo parse accetta un InputSource, quindi non c’era motivo di utilizzare nemmeno un FileReader. Modifica dell’ultima riga del codice sopra a

 xr.parse( new InputSource( filename )); 

funziona bene

Vorrei sottolineare che uno dei problemi è se le stesse risorse si trovano in più file jar. Diciamo che vuoi leggere /org/node/foo.txt, ma non da un file, ma da ogni singolo file jar.

Ho incontrato questo stesso problema diverse volte prima. Speravo in JDK 7 che qualcuno avrebbe scritto un filesystem classpath, ma purtroppo non ancora.

Spring ha la class Resource che ti permette di caricare le risorse del classpath abbastanza bene.

Ho scritto un piccolo prototipo per risolvere questo problema di lettura delle risorse da più file jar. Il prototipo non gestisce ogni caso limite, ma gestisce la ricerca di risorse nelle directory contenute nei file jar.

Ho usato Stack Overflow per un bel po ‘. Questa è la seconda risposta che ricordo di aver risposto a una domanda, quindi perdonami se vado troppo a lungo (è la mia natura).

Questo è un lettore di risorse prototipo. Il prototipo è privo di robusto controllo degli errori.

Ho due file jar prototipo che ho installato.

  <pre> <dependency> <groupId>invoke</groupId> <artifactId>invoke</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>node</groupId> <artifactId>node</artifactId> <version>1.0-SNAPSHOT</version> </dependency> 

I file jar hanno ciascuno un file in / org / node / denominato resource.txt.

Questo è solo un prototipo di come sarebbe un gestore con classpath: // Ho anche un resource.foo.txt nelle mie risorse locali per questo progetto.

Li prende tutti e li stampa fuori.

   package com.foo; import java.io.File; import java.io.FileReader; import java.io.InputStreamReader; import java.io.Reader; import java.net.URI; import java.net.URL; import java.util.Enumeration; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * Prototype resource reader. * This prototype is devoid of error checking. * * * I have two prototype jar files that I have setup. * 
 *  * invoke * invoke * 1.0-SNAPSHOT *  * *  * node * node * 1.0-SNAPSHOT *  * 

* The jar files each have a file under /org/node/ called resource.txt. *
* This is just a prototype of what a handler would look like with classpath:// * I also have a resource.foo.txt in my local resources for this project. *
*/ public class ClasspathReader { public static void main(String[] args) throws Exception { /* This project includes two jar files that each have a resource located in /org/node/ called resource.txt. */ /* Name space is just a device I am using to see if a file in a dir starts with a name space. Think of namespace like a file extension but it is the start of the file not the end. */ String namespace = "resource"; //someResource is classpath. String someResource = args.length > 0 ? args[0] : //"classpath:///org/node/resource.txt"; It works with files "classpath:///org/node/"; //It also works with directories URI someResourceURI = URI.create(someResource); System.out.println("URI of resource = " + someResourceURI); someResource = someResourceURI.getPath(); System.out.println("PATH of resource =" + someResource); boolean isDir = !someResource.endsWith(".txt"); /** Classpath resource can never really start with a starting slash. * Logically they do, but in reality you have to strip it. * This is a known behavior of classpath resources. * It works with a slash unless the resource is in a jar file. * Bottom line, by stripping it, it always works. */ if (someResource.startsWith("/")) { someResource = someResource.substring(1); } /* Use the ClassLoader to lookup all resources that have this name. Look for all resources that match the location we are looking for. */ Enumeration resources = null; /* Check the context classloader first. Always use this if available. */ try { resources = Thread.currentThread().getContextClassLoader().getResources(someResource); } catch (Exception ex) { ex.printStackTrace(); } if (resources == null || !resources.hasMoreElements()) { resources = ClasspathReader.class.getClassLoader().getResources(someResource); } //Now iterate over the URLs of the resources from the classpath while (resources.hasMoreElements()) { URL resource = resources.nextElement(); /* if the resource is a file, it just means that we can use normal mechanism to scan the directory. */ if (resource.getProtocol().equals("file")) { //if it is a file then we can handle it the normal way. handleFile(resource, namespace); continue; } System.out.println("Resource " + resource); /* Split up the string that looks like this: jar:file:/Users/rick/.m2/repository/invoke/invoke/1.0-SNAPSHOT/invoke-1.0-SNAPSHOT.jar!/org/node/ into this /Users/rick/.m2/repository/invoke/invoke/1.0-SNAPSHOT/invoke-1.0-SNAPSHOT.jar and this /org/node/ */ String[] split = resource.toString().split(":"); String[] split2 = split[2].split("!"); String zipFileName = split2[0]; String sresource = split2[1]; System.out.printf("After split zip file name = %s," + " \nresource in zip %s \n", zipFileName, sresource); /* Open up the zip file. */ ZipFile zipFile = new ZipFile(zipFileName); /* Iterate through the entries. */ Enumeration entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); /* If it is a directory, then skip it. */ if (entry.isDirectory()) { continue; } String entryName = entry.getName(); System.out.printf("zip entry name %s \n", entryName); /* If it does not start with our someResource String then it is not our resource so continue. */ if (!entryName.startsWith(someResource)) { continue; } /* the fileName part from the entry name. * where /foo/bar/foo/bee/bar.txt, bar.txt is the file */ String fileName = entryName.substring(entryName.lastIndexOf("/") + 1); System.out.printf("fileName %s \n", fileName); /* See if the file starts with our namespace and ends with our extension. */ if (fileName.startsWith(namespace) && fileName.endsWith(".txt")) { /* If you found the file, print out the contents fo the file to System.out.*/ try (Reader reader = new InputStreamReader(zipFile.getInputStream(entry))) { StringBuilder builder = new StringBuilder(); int ch = 0; while ((ch = reader.read()) != -1) { builder.append((char) ch); } System.out.printf("zip fileName = %s\n\n####\n contents of file %s\n###\n", entryName, builder); } catch (Exception ex) { ex.printStackTrace(); } } //use the entry to see if it's the file '1.txt' //Read from the byte using file.getInputStream(entry) } } } /** * The file was on the file system not a zip file, * this is here for completeness for this example. * otherwise. * * @param resource * @param namespace * @throws Exception */ private static void handleFile(URL resource, String namespace) throws Exception { System.out.println("Handle this resource as a file " + resource); URI uri = resource.toURI(); File file = new File(uri.getPath()); if (file.isDirectory()) { for (File childFile : file.listFiles()) { if (childFile.isDirectory()) { continue; } String fileName = childFile.getName(); if (fileName.startsWith(namespace) && fileName.endsWith("txt")) { try (FileReader reader = new FileReader(childFile)) { StringBuilder builder = new StringBuilder(); int ch = 0; while ((ch = reader.read()) != -1) { builder.append((char) ch); } System.out.printf("fileName = %s\n\n####\n contents of file %s\n###\n", childFile, builder); } catch (Exception ex) { ex.printStackTrace(); } } } } else { String fileName = file.getName(); if (fileName.startsWith(namespace) && fileName.endsWith("txt")) { try (FileReader reader = new FileReader(file)) { StringBuilder builder = new StringBuilder(); int ch = 0; while ((ch = reader.read()) != -1) { builder.append((char) ch); } System.out.printf("fileName = %s\n\n####\n contents of file %s\n###\n", fileName, builder); } catch (Exception ex) { ex.printStackTrace(); } } } } }

package com.foo; import java.io.File; import java.io.FileReader; import java.io.InputStreamReader; import java.io.Reader; import java.net.URI; import java.net.URL; import java.util.Enumeration; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * Prototype resource reader. * This prototype is devoid of error checking. * * * I have two prototype jar files that I have setup. *

 *  * invoke * invoke * 1.0-SNAPSHOT *  * *  * node * node * 1.0-SNAPSHOT *  * 

* The jar files each have a file under /org/node/ called resource.txt. *
* This is just a prototype of what a handler would look like with classpath:// * I also have a resource.foo.txt in my local resources for this project. *
*/ public class ClasspathReader { public static void main(String[] args) throws Exception { /* This project includes two jar files that each have a resource located in /org/node/ called resource.txt. */ /* Name space is just a device I am using to see if a file in a dir starts with a name space. Think of namespace like a file extension but it is the start of the file not the end. */ String namespace = "resource"; //someResource is classpath. String someResource = args.length > 0 ? args[0] : //"classpath:///org/node/resource.txt"; It works with files "classpath:///org/node/"; //It also works with directories URI someResourceURI = URI.create(someResource); System.out.println("URI of resource = " + someResourceURI); someResource = someResourceURI.getPath(); System.out.println("PATH of resource =" + someResource); boolean isDir = !someResource.endsWith(".txt"); /** Classpath resource can never really start with a starting slash. * Logically they do, but in reality you have to strip it. * This is a known behavior of classpath resources. * It works with a slash unless the resource is in a jar file. * Bottom line, by stripping it, it always works. */ if (someResource.startsWith("/")) { someResource = someResource.substring(1); } /* Use the ClassLoader to lookup all resources that have this name. Look for all resources that match the location we are looking for. */ Enumeration resources = null; /* Check the context classloader first. Always use this if available. */ try { resources = Thread.currentThread().getContextClassLoader().getResources(someResource); } catch (Exception ex) { ex.printStackTrace(); } if (resources == null || !resources.hasMoreElements()) { resources = ClasspathReader.class.getClassLoader().getResources(someResource); } //Now iterate over the URLs of the resources from the classpath while (resources.hasMoreElements()) { URL resource = resources.nextElement(); /* if the resource is a file, it just means that we can use normal mechanism to scan the directory. */ if (resource.getProtocol().equals("file")) { //if it is a file then we can handle it the normal way. handleFile(resource, namespace); continue; } System.out.println("Resource " + resource); /* Split up the string that looks like this: jar:file:/Users/rick/.m2/repository/invoke/invoke/1.0-SNAPSHOT/invoke-1.0-SNAPSHOT.jar!/org/node/ into this /Users/rick/.m2/repository/invoke/invoke/1.0-SNAPSHOT/invoke-1.0-SNAPSHOT.jar and this /org/node/ */ String[] split = resource.toString().split(":"); String[] split2 = split[2].split("!"); String zipFileName = split2[0]; String sresource = split2[1]; System.out.printf("After split zip file name = %s," + " \nresource in zip %s \n", zipFileName, sresource); /* Open up the zip file. */ ZipFile zipFile = new ZipFile(zipFileName); /* Iterate through the entries. */ Enumeration entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); /* If it is a directory, then skip it. */ if (entry.isDirectory()) { continue; } String entryName = entry.getName(); System.out.printf("zip entry name %s \n", entryName); /* If it does not start with our someResource String then it is not our resource so continue. */ if (!entryName.startsWith(someResource)) { continue; } /* the fileName part from the entry name. * where /foo/bar/foo/bee/bar.txt, bar.txt is the file */ String fileName = entryName.substring(entryName.lastIndexOf("/") + 1); System.out.printf("fileName %s \n", fileName); /* See if the file starts with our namespace and ends with our extension. */ if (fileName.startsWith(namespace) && fileName.endsWith(".txt")) { /* If you found the file, print out the contents fo the file to System.out.*/ try (Reader reader = new InputStreamReader(zipFile.getInputStream(entry))) { StringBuilder builder = new StringBuilder(); int ch = 0; while ((ch = reader.read()) != -1) { builder.append((char) ch); } System.out.printf("zip fileName = %s\n\n####\n contents of file %s\n###\n", entryName, builder); } catch (Exception ex) { ex.printStackTrace(); } } //use the entry to see if it's the file '1.txt' //Read from the byte using file.getInputStream(entry) } } } /** * The file was on the file system not a zip file, * this is here for completeness for this example. * otherwise. * * @param resource * @param namespace * @throws Exception */ private static void handleFile(URL resource, String namespace) throws Exception { System.out.println("Handle this resource as a file " + resource); URI uri = resource.toURI(); File file = new File(uri.getPath()); if (file.isDirectory()) { for (File childFile : file.listFiles()) { if (childFile.isDirectory()) { continue; } String fileName = childFile.getName(); if (fileName.startsWith(namespace) && fileName.endsWith("txt")) { try (FileReader reader = new FileReader(childFile)) { StringBuilder builder = new StringBuilder(); int ch = 0; while ((ch = reader.read()) != -1) { builder.append((char) ch); } System.out.printf("fileName = %s\n\n####\n contents of file %s\n###\n", childFile, builder); } catch (Exception ex) { ex.printStackTrace(); } } } } else { String fileName = file.getName(); if (fileName.startsWith(namespace) && fileName.endsWith("txt")) { try (FileReader reader = new FileReader(file)) { StringBuilder builder = new StringBuilder(); int ch = 0; while ((ch = reader.read()) != -1) { builder.append((char) ch); } System.out.printf("fileName = %s\n\n####\n contents of file %s\n###\n", fileName, builder); } catch (Exception ex) { ex.printStackTrace(); } } } } }

Puoi vedere un esempio più completo qui con l’output di esempio.

Al di fuori della tua tecnica, perché non usare la class Java JarFile standard per ottenere i riferimenti che desideri? Da lì la maggior parte dei tuoi problemi dovrebbe andare via.

Se si utilizzano le risorse in modo esteso, si potrebbe prendere in considerazione l’utilizzo di Commons VFS .

Supporta anche: * File locali * FTP, SFTP * HTTP e HTTPS * File temporanei “normale FS backed * * Zip, Jar e Tar (non compresso, tgz o tbz2) * gzip e bzip2 * risorse * ram -” ramdrive “* mime

C’è anche JBoss VFS – ma non è molto documentato.

Ho 2 file CSV che uso per leggere i dati. Il programma java viene esportato come un file jar eseguibile. Quando lo esporti, scoprirai che non esporta le tue risorse con esso.

Ho aggiunto una cartella sotto progetto chiamata data in eclipse. In quella cartella ho archiviato i miei file CSV.

Quando ho bisogno di fare riferimento a quei file, lo faccio in questo modo …

 private static final String ZIP_FILE_LOCATION_PRIMARY = "free-zipcode-database-Primary.csv"; private static final String ZIP_FILE_LOCATION = "free-zipcode-database.csv"; private static String getFileLocation(){ String loc = new File("").getAbsolutePath() + File.separatorChar + "data" + File.separatorChar; if (usePrimaryZipCodesOnly()){ loc = loc.concat(ZIP_FILE_LOCATION_PRIMARY); } else { loc = loc.concat(ZIP_FILE_LOCATION); } return loc; } 

Quindi, quando inserisci il barattolo in una posizione in modo che possa essere eseguito tramite riga di comando, assicurati di aggiungere la cartella di dati con le risorse nella stessa posizione del file jar.