Как мне прочитать файл ресурса из файла ja r Java?

Я пытаюсь получить доступ к XML-файлу внутри jar-файла из отдельного jar-файла, работающего как настольное приложение. Я могу получить URL-адрес нужного мне файла, но когда я передаю его в FileReader (в виде строки), я получаю исключение FileNotFoundException, говорящее: "Имя файла, имя каталога или синтаксис метки тома неверен".

Для справки: у меня нет проблем с чтением ресурсов изображения из того же jar-файла и передачей URL-адреса в конструктор ImageIcon. Кажется, это указывает на то, что метод, который я использую для получения URL-адреса, правильный.

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

Внутри класса ServicesLoader у меня есть

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

Что не так с использованием этой техники для чтения файла XML?

8 ответов

Решение

Проблема заключалась в том, что я зашел слишком далеко в вызове метода синтаксического анализа XMLReader. Метод parse принимает InputSource, поэтому не было причин даже использовать FileReader. Изменение последней строки кода выше на

xr.parse( new InputSource( filename ));

работает просто отлично.

Похоже, вы хотите использовать java.lang.Class.getResourceAsStream(String), увидеть

http://java.sun.com/javase/6/docs/api/java/lang/Class.html

Вы не говорите, если это настольное или веб-приложение. Я бы использовал getResourceAsStream() метод из соответствующего ClassLoader, если это рабочий стол, или Context, если это веб-приложение.

Похоже, что вы используете URL.toString результат в качестве аргумента FileReader конструктор. URL.toString немного сломан, и вместо этого вы должны обычно использовать url.toURI().toString(), В любом случае строка не является путем к файлу.

Вместо этого вы должны либо:

  • Пройти URL в ServicesLoader и пусть звонит openStream или похожие.
  • использование Class.getResourceAsStream и просто передать поток, возможно, внутри InputSource, (Не забудьте проверить на нулевые значения, так как API немного запутан.)

Я хотел бы отметить, что одна проблема заключается в том, что если одни и те же ресурсы находятся в нескольких JAR-файлах. Допустим, вы хотите прочитать /org/node/foo.txt, но не из одного файла, а из каждого файла JAR.

Я сталкивался с этой же проблемой несколько раз прежде. В JDK 7 я надеялся, что кто-то напишет файловую систему classpath, но, увы, пока.

В Spring есть класс Resource, который позволяет вам очень приятно загружать ресурсы classpath.

Я написал небольшой прототип, чтобы решить эту проблему чтения ресурсов из нескольких файлов JAR. Прототип не обрабатывает каждый крайний случай, но он обрабатывает поиск ресурсов в каталогах, которые находятся в jar-файлах.

Я использовал Stack Overflow довольно давно. Это второй ответ, который я помню, отвечая на вопрос, так что прости меня, если я пойду слишком долго (это моя натура).

Это прототип ресурса для чтения. Прототип лишен надежной проверки ошибок.

У меня есть два файла jar-прототипа, которые я настроил.

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

Каждый из файлов jar имеет файл в / org / node / с именем resource.txt.

Это всего лишь прототип того, как будет выглядеть обработчик с classpath:// У меня также есть resource.foo.txt в моих локальных ресурсах для этого проекта.

Он собирает их все и распечатывает.

   

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

                }

            }
        }

    }


   

Вы можете увидеть более полный пример здесь с примером вывода.

Вот пример кода о том, как правильно читать файл внутри файла jar (в данном случае, текущего исполняемого файла jar)

Просто измените исполняемый файл на путь к вашему файлу jar, если он не является текущим запущенным.

Затем измените filePath на путь к файлу, который вы хотите использовать внутри файла jar. IE, если ваш файл находится в

someJar.jar\img\test.gif

. Установите для filePath значение "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();
}

У меня есть 2 файла CSV, которые я использую для чтения данных. Java-программа экспортируется как исполняемый файл JAR. Когда вы экспортируете его, вы поймете, что он не экспортирует ваши ресурсы вместе с ним.

Я добавил папку в проекте под названием data в eclipse. В этой папке я хранил свои CSV-файлы.

Когда мне нужно сослаться на эти файлы, я делаю это так...

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

Затем, когда вы поместите jar-файл в такое место, чтобы его можно было запустить из командной строки, убедитесь, что вы добавили папку данных с ресурсами в то же место, что и файл jar-файла.

Вне вашей техники, почему бы не использовать стандартный класс Java JarFile для получения нужных ссылок? Оттуда большинство ваших проблем должно уйти.

Если вы интенсивно используете ресурсы, вы можете рассмотреть возможность использования Commons VFS.

Также поддерживаются: * Локальные файлы * FTP, SFTP * HTTP и HTTPS * Временные файлы "с обычной FS-поддержкой) * Zip, Jar и Tar (без сжатия, tgz или tbz2) * gzip и bzip2 * ресурсы * ram - "ramdrive" * mime

Есть также JBoss VFS - но это не очень задокументировано.

Другие вопросы по тегам