简体   繁体   English

如何从 Java jar 文件中读取资源文件?

[英]How do I read a resource file from a Java jar file?

I'm trying to access an XML file within a jar file, from a separate jar that's running as a desktop application.我正在尝试从作为桌面应用程序运行的单独 jar 访问 jar 文件中的 XML 文件。 I can get the URL to the file I need, but when I pass that to a FileReader (as a String) I get a FileNotFoundException saying "The file name, directory name, or volume label syntax is incorrect."我可以获得我需要的文件的 URL,但是当我将它传递给 FileReader(作为字符串)时,我得到一个 FileNotFoundException 说“文件名、目录名或卷标语法不正确。”

As a point of reference, I have no trouble reading image resources from the same jar, passing the URL to an ImageIcon constructor.作为参考,我从同一个 jar 中读取图像资源没有问题,将 URL 传递给 ImageIcon 构造函数。 This seems to indicate that the method I'm using to get the URL is correct.这似乎表明我用来获取 URL 的方法是正确的。

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

Inside the ServicesLoader class I have在 ServicesLoader 类中我有

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

What's wrong with using this technique to read the XML file?使用这种技术读取 XML 文件有什么问题?

Looks like you want to use java.lang.Class.getResourceAsStream(String) , see看起来你想使用java.lang.Class.getResourceAsStream(String) ,见

https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#getResourceAsStream-java.lang.String- https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#getResourceAsStream-java.lang.String-

You don't say if this is a desktop or web app.您不会说这是桌面应用程序还是 Web 应用程序。 I would use the getResourceAsStream() method from an appropriate ClassLoader if it's a desktop or the Context if it's a web app.如果是桌面,我会使用来自适当 ClassLoader 的getResourceAsStream()方法,如果它是 Web 应用程序,我会使用 Context。

It looks as if you are using the URL.toString result as the argument to the FileReader constructor.看起来好像您使用URL.toString结果作为FileReader构造函数的参数。 URL.toString is a bit broken, and instead you should generally use url.toURI().toString() . URL.toString有点坏,你通常应该使用url.toURI().toString() In any case, the string is not a file path.在任何情况下,该字符串都不是文件路径。

Instead, you should either:相反,您应该:

  • Pass the URL to ServicesLoader and let it call openStream or similar.URL传递给ServicesLoader并让它调用openStream或类似方法。
  • Use Class.getResourceAsStream and just pass the stream over, possibly inside an InputSource .使用Class.getResourceAsStream并传递流,可能在InputSource内部。 (Remember to check for nulls as the API is a bit messy.) (记住检查空值,因为 API 有点混乱。)

The problem was that I was going a step too far in calling the parse method of XMLReader.问题是我在调用 XMLReader 的 parse 方法方面走得太远了。 The parse method accepts an InputSource, so there was no reason to even use a FileReader. parse 方法接受一个 InputSource,所以甚至没有理由使用 FileReader。 Changing the last line of the code above to将上面代码的最后一行更改为

xr.parse( new InputSource( filename ));

works just fine.工作得很好。

I'd like to point out that one issues is what if the same resources are in multiple jar files.我想指出一个问题是,如果相同的资源在多个 jar 文件中会怎样。 Let's say you want to read /org/node/foo.txt, but not from one file, but from each and every jar file.假设您想读取 /org/node/foo.txt,但不是从一个文件中,而是从每个 jar 文件中读取。

I have run into this same issue several times before.我以前多次遇到过同样的问题。 I was hoping in JDK 7 that someone would write a classpath filesystem, but alas not yet.我希望在 JDK 7 中有人会编写类路径文件系统,但可惜还没有。

Spring has the Resource class which allows you to load classpath resources quite nicely. Spring 有 Resource 类,它允许您很好地加载类路径资源。

I wrote a little prototype to solve this very problem of reading resources form multiple jar files.我写了一个小原型来解决这个从多个 jar 文件中读取资源的问题。 The prototype does not handle every edge case, but it does handle looking for resources in directories that are in the jar files.该原型并不处理所有边缘情况,但它确实处理在 jar 文件中的目录中查找资源。

I have used Stack Overflow for quite sometime.我使用 Stack Overflow 已经有一段时间了。 This is the second answer that I remember answering a question so forgive me if I go too long (it is my nature).这是我记得回答问题的第二个答案,所以如果我回答的时间太长,请原谅我(这是我的本性)。

This is a prototype resource reader.这是一个原型资源阅读器。 The prototype is devoid of robust error checking.原型没有强大的错误检查。

I have two prototype jar files that I have setup.我有两个已设置的原型 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>

The jar files each have a file under /org/node/ called resource.txt.每个 jar 文件在 /org/node/ 下都有一个名为 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.这只是使用 classpath:// 处理程序的原型 我在我的本地资源中也有一个用于该项目的 resource.foo.txt。

It picks them all up and prints them out.它把它们全部捡起来并打印出来。



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

                }

            }
        }

    }


   

You can see a fuller example here with the sample output. 您可以在此处查看带有示例输出的更完整示例。

Here's a sample code on how to read a file properly inside a jar file (in this case, the current executing jar file)这是有关如何正确读取 jar 文件(在本例中为当前执行的 jar 文件)中的文件的示例代码

Just change executable with the path of your jar file if it is not the current running one.如果它不是当前正在运行的文件,只需使用 jar 文件的路径更改可执行文件。

Then change the filePath to the path of the file you want to use inside the jar file.然后将 filePath 更改为要在 jar 文件中使用的文件的路径。 IE if your file is in IE 如果您的文件在

someJar.jar\\img\\test.gif someJar.jar\\img\\test.gif

. . Set the filePath to "img\\test.gif"将文件路径设置为“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();
}

I have 2 CSV files that I use to read data.我有 2 个用于读取数据的 CSV 文件。 The java program is exported as a runnable jar file. java 程序导出为可运行的 jar 文件。 When you export it, you will figure out it doesn't export your resources with it.当您导出它时,您会发现它不会随它一起导出您的资源。

I added a folder under project called data in eclipse.我在eclipse中的项目下添加了一个名为data的文件夹。 In that folder i stored my csv files.在那个文件夹中,我存储了我的 csv 文件。

When I need to reference those files I do it like this...当我需要引用这些文件时,我会这样做...

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

Then when you put the jar in a location so it can be ran via commandline, make sure that you add the data folder with the resources into the same location as the jar file.然后,当您将 jar 放在可以通过命令行运行的位置时,请确保将包含资源的数据文件夹添加到与 jar 文件相同的位置。

Outside of your technique, why not use the standard Java JarFile class to get the references you want?在您的技术之外,为什么不使用标准的 Java JarFile 类来获取您想要的引用呢? From there most of your problems should go away.从那里你的大部分问题应该消失。

If you use resources extensively, you might consider using Commons VFS .如果您广泛使用资源,您可以考虑使用Commons VFS

Also supports: * Local Files * FTP, SFTP * HTTP and HTTPS * Temporary Files "normal FS backed) * Zip, Jar and Tar (uncompressed, tgz or tbz2) * gzip and bzip2 * resources * ram - "ramdrive" * mime还支持: * 本地文件 * FTP、SFTP * HTTP 和 HTTPS * 临时文件“普通 FS 支持) * Zip、Jar 和 Tar(未压缩、tgz 或 tbz2) * gzip 和 bzip2 * 资源 * ram - "ramdrive" * mime

There's also JBoss VFS - but it's not much documented.还有JBoss VFS - 但没有太多记录。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM