it-swarm.com.de

Wie lese ich eine Ressourcendatei aus einer Java jar-Datei?

Ich versuche, von einer separaten JAR-Datei, die als Desktop-Anwendung ausgeführt wird, auf eine XML-Datei in einer JAR-Datei zuzugreifen. Ich kann die URL zu der Datei abrufen, die ich benötige, aber wenn ich diese an einen FileReader (als String) übergebe, wird eine FileNotFoundException mit der Meldung "Der Dateiname, der Verzeichnisname oder die Datenträgerbezeichnungssyntax sind falsch."

Als Referenz kann ich problemlos Bildressourcen aus derselben JAR-Datei lesen und die URL an einen ImageIcon-Konstruktor übergeben. Dies scheint darauf hinzudeuten, dass die von mir verwendete Methode zum Abrufen der URL korrekt ist.

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

Innerhalb der ServicesLoader-Klasse, die ich habe

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

Was ist falsch daran, diese Technik zum Lesen der XML-Datei zu verwenden?

57
Bill the Lizard

Das Problem war, dass ich beim Aufrufen der Analysemethode von XMLReader einen Schritt zu weit gegangen bin. Die parse-Methode akzeptiert eine InputSource, sodass es keinen Grund gab, überhaupt einen FileReader zu verwenden. Ändern Sie die letzte Zeile des obigen Codes in

xr.parse( new InputSource( filename ));

funktioniert gut.

4
Bill the Lizard

Sieht so aus, als ob Sie Java.lang.Class.getResourceAsStream(String) verwenden möchten

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

59
iny

Sie sagen nicht, ob dies eine Desktop- oder Web-App ist. Ich würde die getResourceAsStream() -Methode von einem geeigneten ClassLoader verwenden, wenn es sich um einen Desktop handelt, oder den Context, wenn es sich um eine Webanwendung handelt.

5
duffymo

Es sieht so aus, als würden Sie das Ergebnis URL.toString Als Argument für den Konstruktor FileReader verwenden. URL.toString Ist ein bisschen kaputt, und stattdessen sollten Sie im Allgemeinen url.toURI().toString() verwenden. In jedem Fall ist die Zeichenfolge kein Dateipfad.

Stattdessen sollten Sie entweder:

  • Übergeben Sie das URL an ServicesLoader und lassen Sie es openStream oder ähnliches aufrufen.
  • Verwenden Sie Class.getResourceAsStream Und leiten Sie den Stream einfach weiter, möglicherweise in einem InputSource. (Denken Sie daran, auf Nullen zu prüfen, da die API etwas unübersichtlich ist.)

Ich möchte darauf hinweisen, dass eines der Probleme darin besteht, dass sich dieselben Ressourcen in mehreren JAR-Dateien befinden. Angenommen, Sie möchten /org/node/foo.txt lesen, aber nicht aus einer Datei, sondern aus jeder einzelnen JAR-Datei.

Ich bin schon mehrmals auf dasselbe Problem gestoßen. Ich hatte in JDK 7 gehofft, dass jemand ein Klassenpfad-Dateisystem schreiben würde, aber leider noch nicht.

Spring hat die Ressourcenklasse, mit der Sie Klassenpfadressourcen recht gut laden können.

Ich habe einen kleinen Prototyp geschrieben, um genau dieses Problem des Lesens von Ressourcen aus mehreren JAR-Dateien zu lösen. Der Prototyp verarbeitet nicht jeden Edge-Fall, sondern sucht nach Ressourcen in Verzeichnissen, die sich in den JAR-Dateien befinden.

Ich benutze Stack Overflow schon seit einiger Zeit. Dies ist die zweite Antwort, bei der ich mich erinnere, eine Frage beantwortet zu haben. Verzeih mir, wenn ich zu lange gehe (das liegt in meiner Natur).

Dies ist ein Prototyp eines Ressourcenlesegeräts. Dem Prototyp fehlt eine robuste Fehlerprüfung.

Ich habe zwei Prototyp-JAR-Dateien, die ich eingerichtet habe.

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

Die JAR-Dateien haben jeweils eine Datei unter/org/node/mit dem Namen resource.txt.

Dies ist nur ein Prototyp dessen, wie ein Handler mit classpath aussehen würde: // Ich habe auch eine resource.foo.txt in meinen lokalen Ressourcen für dieses Projekt.

Es nimmt sie alle auf und druckt sie aus.



    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.
    * <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>
    * </pre>
    * The jar files each have a file under /org/node/ called resource.txt.
    * <br />
    * 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.
    * <br />
    */
    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();
                    }

                }

            }
        }

    }


   

Hier sehen Sie ein ausführlicheres Beispiel mit der Beispielausgabe.

2
RickHigh

Hier ist ein Beispielcode zum korrekten Lesen einer Datei in einer JAR-Datei (in diesem Fall die aktuell ausgeführte JAR-Datei).

Ändern Sie einfach die ausführbare Datei mit dem Pfad Ihrer JAR-Datei, wenn es sich nicht um die aktuell ausgeführte handelt.

Ändern Sie dann den Dateipfad in den Pfad der Datei, die Sie in der JAR-Datei verwenden möchten. I.E. Wenn Ihre Datei in ist

someJar.jar\img\test.gif

. Setze den filePath auf "img\test.gif"

File executable = new File(BrowserViewControl.class.getProtectionDomain().getCodeSource().getLocation().toURI());
JarFile jar = new JarFile(executable);
InputStream fileInputStreamReader = jar.getInputStream(jar.getJarEntry(filePath));
byte[] bytes = new byte[fileInputStreamReader.available()];

int sizeOrig = fileInputStreamReader.available();
int size = fileInputStreamReader.available();
int offset = 0;
while (size != 0){
    fileInputStreamReader.read(bytes, offset, size);
    offset = sizeOrig - fileInputStreamReader.available();
    size = fileInputStreamReader.available();
}
0
MDuh

Ich habe 2 CSV-Dateien, mit denen ich Daten lese. Das Programm Java wird als ausführbare JAR-Datei exportiert. Wenn Sie es exportieren, werden Sie feststellen, dass es Ihre Ressourcen damit nicht exportiert.

In Eclipse habe ich unter Projekt den Ordner data hinzugefügt. In diesem Ordner habe ich meine CSV-Dateien gespeichert.

Wenn ich auf diese Dateien verweisen muss, mache ich das so ...

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

Wenn Sie dann die JAR-Datei an einem Ort ablegen, an dem sie über die Befehlszeile ausgeführt werden kann, stellen Sie sicher, dass Sie den Datenordner mit den Ressourcen am selben Ort wie die JAR-Datei hinzufügen.

0
dayv2005

Verwenden Sie außerhalb Ihrer Technik die Standardklasse Java JarFile class , um die gewünschten Referenzen zu erhalten. Von dort aus sollten die meisten Ihrer Probleme behoben sein.

0
GaryF

Wenn Sie Ressourcen häufig verwenden, sollten Sie Commons VFS verwenden.

Unterstützt auch: * Lokale Dateien * FTP, SFTP * HTTP und HTTPS * Temporäre Dateien "normal FS gesichert) * Zip, Jar und Tar (unkomprimiert, tgz oder tbz2) * gzip und bzip2 * Ressourcen * ram - "ramdrive" * mime

Es gibt auch JBoss VFS - aber es ist nicht viel dokumentiert.

0
Ondra Žižka