Как прочитать файл ресурсов из файла jar Java?

StackOverflow https://stackoverflow.com/questions/403256

  •  03-07-2019
  •  | 
  •  

Вопрос

Я пытаюсь получить доступ к 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-файла?

Это было полезно?

Решение 4

Проблема заключалась в том, что я зашел слишком далеко в вызове метода синтаксического анализа XMLReader.Метод анализа принимает 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(java.lang.String)

Вы не говорите, настольное ли это приложение или веб-приложение.я бы использовал 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 кто-нибудь напишет файловую систему с путями к классам, но, увы, пока нет.

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

Я написал небольшой прототип, чтобы решить эту самую проблему чтения ресурсов из нескольких файлов 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/ с именем resources.txt.

Это всего лишь прототип того, как будет выглядеть обработчик с ClassPath: // У меня также есть ресурс.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();
                    }

                }

            }
        }

    }


   

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

Помимо вашей техники, почему бы не использовать стандартную Java Класс ДжарФайл чтобы получить нужные ссылки?После этого большинство ваших проблем должно исчезнуть.

Если вы активно используете ресурсы, вы можете рассмотреть возможность использования Коммонс ВФС.

Также поддерживает:* Локальные файлы * ftp, sftp * http и https * Временные файлы "Нормальный Fs поддерживают) * zip, jar и tar (несжатые, tgz или tbz2) * gzip и bzip2 * resources * ram -" ramdrive " * mime

Есть также ДжейБосс ВФС - но это мало задокументировано.

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

Я добавил папку в проект под названием data in 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.

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

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

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

someJar.jar\img est.gif

.Установите путь к файлу «img est.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();
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top