web-dev-qa-db-fra.com

Comment lire un fichier de ressources à partir d'un fichier jar Java?

J'essaie d'accéder à un fichier XML dans un fichier jar, à partir d'un bocal séparé qui s'exécute en tant qu'application de bureau. Je peux obtenir l'URL du fichier dont j'ai besoin, mais lorsque je le transmets à un FileReader (sous forme de chaîne), j'obtiens une FileNotFoundException disant "Le nom de fichier, le nom de répertoire ou la syntaxe de l'étiquette de volume est incorrect."

En tant que point de référence, je n'ai aucun problème à lire les ressources d'image à partir du même pot, en passant l'URL à un constructeur ImageIcon. Cela semble indiquer que la méthode que j'utilise pour obtenir l'URL est correcte.

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

Dans la classe ServicesLoader que j'ai

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

Quel est le problème avec l'utilisation de cette technique pour lire le fichier XML?

57
Bill the Lizard

Le problème était que j'allais trop loin en appelant la méthode d'analyse de XMLReader. La méthode d'analyse accepte un InputSource, il n'y avait donc aucune raison d'utiliser même un FileReader. Changer la dernière ligne du code ci-dessus en

xr.parse( new InputSource( filename ));

fonctionne très bien.

4
Bill the Lizard

On dirait que vous voulez utiliser Java.lang.Class.getResourceAsStream(String), voir

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

59
iny

Vous ne dites pas s'il s'agit d'une application de bureau ou Web. J'utiliserais la méthode getResourceAsStream() à partir d'un ClassLoader approprié s'il s'agit d'un bureau ou du Context s'il s'agit d'une application Web.

5
duffymo

Il semble que vous utilisiez le résultat URL.toString Comme argument du constructeur FileReader. URL.toString Est un peu cassé, et vous devriez généralement utiliser url.toURI().toString(). Dans tous les cas, la chaîne n'est pas un chemin de fichier.

Au lieu de cela, vous devez soit:

  • Passez le URL à ServicesLoader et laissez-le appeler openStream ou similaire.
  • Utilisez Class.getResourceAsStream Et passez simplement le flux, éventuellement à l'intérieur d'un InputSource. (N'oubliez pas de vérifier les valeurs nulles car l'API est un peu compliquée.)
4

Je voudrais souligner qu'un problème est de savoir si les mêmes ressources sont dans plusieurs fichiers jar. Disons que vous voulez lire /org/node/foo.txt, mais pas à partir d'un seul fichier, mais à partir de chaque fichier jar.

J'ai rencontré ce même problème plusieurs fois auparavant. J'espérais dans JDK 7 que quelqu'un écrirait un système de fichiers classpath, mais hélas pas encore.

Spring a la classe Resource qui vous permet de charger assez bien les ressources classpath.

J'ai écrit un petit prototype pour résoudre ce problème même de lecture des ressources à partir de plusieurs fichiers jar. Le prototype ne gère pas tous les cas Edge, mais il gère la recherche de ressources dans les répertoires qui se trouvent dans les fichiers jar.

J'utilise Stack Overflow depuis un certain temps. C'est la deuxième réponse dont je me souviens avoir répondu à une question alors pardonnez-moi si je vais trop longtemps (c'est ma nature).

Il s'agit d'un prototype de lecteur de ressources. Le prototype est dépourvu de vérification d'erreur robuste.

J'ai deux fichiers jar prototypes que j'ai configurés.

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

Les fichiers jar ont chacun un fichier sous/org/node/appelé resource.txt.

Ceci est juste un prototype de ce à quoi un gestionnaire ressemblerait avec classpath: // J'ai également un resource.foo.txt dans mes ressources locales pour ce projet.

Il les ramasse tous et les imprime.



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

                }

            }
        }

    }


   

Vous pouvez voir un exemple plus complet ici avec l'exemple de sortie.

2
RickHigh

Voici un exemple de code sur la façon de lire un fichier correctement dans un fichier jar (dans ce cas, le fichier jar en cours d'exécution)

Modifiez simplement l'exécutable avec le chemin de votre fichier jar s'il n'est pas celui en cours d'exécution.

Remplacez ensuite filePath par le chemin du fichier que vous souhaitez utiliser dans le fichier jar. C'EST À DIRE. si votre fichier est en

someJar.jar\img\test.gif

. Définissez filePath sur "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

J'ai 2 fichiers CSV que j'utilise pour lire des données. Le programme Java est exporté en tant que fichier jar exécutable. Lorsque vous l'exportez, vous constaterez qu'il n'exporte pas vos ressources avec.

J'ai ajouté un dossier sous projet appelé données dans Eclipse. Dans ce dossier, j'ai stocké mes fichiers csv.

Quand j'ai besoin de référencer ces fichiers, je le fais comme ça ...

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

Ensuite, lorsque vous placez le fichier jar dans un emplacement afin qu'il puisse être exécuté via la ligne de commande, assurez-vous que vous ajoutez le dossier de données avec les ressources au même emplacement que le fichier jar.

0
dayv2005

En dehors de votre technique, pourquoi ne pas utiliser le standard Java classe JarFile pour obtenir les références que vous souhaitez? De là, la plupart de vos problèmes devraient disparaître.

0
GaryF

Si vous utilisez beaucoup de ressources, vous pourriez envisager d'utiliser Commons VFS .

Prend également en charge: * Fichiers locaux * FTP, SFTP * HTTP et HTTPS * Fichiers temporaires "normal FS sauvegardé) * Zip, Jar et Tar (non compressé, tgz ou tbz2) * gzip et bzip2 * ressources * ram - "ramdrive" * mime

Il y a aussi JBoss VFS - mais ce n'est pas beaucoup documenté.

0
Ondra Žižka