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

Posted

技术标签:

【中文标题】如何从 Java jar 文件中读取资源文件?【英文标题】:How do I read a resource file from a Java jar file? 【发布时间】:2008-12-31 15:46:20 【问题描述】:

我正在尝试从作为桌面应用程序运行的单独 jar 访问 jar 文件中的 XML 文件。我可以获得所需文件的 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 文件有什么问题?

【问题讨论】:

你能发布工作代码吗? 确保在使用 Files API 时也使用 unix 文件路径分隔符。 【参考方案1】:

看起来你想使用java.lang.Class.getResourceAsStream(String),请参阅

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

【讨论】:

@JVerstry 你的回答对我很有帮助,遗憾的是这里完全被低估了【参考方案2】:

您不会说这是桌面应用程序还是网络应用程序。如果是桌面,我会使用适当的 ClassLoader 中的 getResourceAsStream() 方法,如果是 Web 应用程序,我会使用 Context。

【讨论】:

【参考方案3】:

看起来您使用URL.toString 结果作为FileReader 构造函数的参数。 URL.toString 有点坏了,你通常应该使用url.toURI().toString()。在任何情况下,字符串都不是文件路径。

相反,您应该:

URL 传递给ServicesLoader 并让它调用openStream 或类似名称。 使用Class.getResourceAsStream,然后将流传递过来,可能在InputSource 内。 (记得检查空值,因为 API 有点乱。)

【讨论】:

【参考方案4】:

问题是我在调用 XMLReader 的 parse 方法时走得太远了。 parse 方法接受 InputSource,因此甚至没有理由使用 FileReader。将上面代码的最后一行更改为

xr.parse( new InputSource( filename ));

工作得很好。

【讨论】:

【参考方案5】:

我想指出一个问题是,如果相同的资源位于多个 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/ 下都有一个名为 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();

You can see a fuller example here with the sample output.

【讨论】:

【参考方案6】:

这是一个示例代码,说明如何在 jar 文件(在本例中为当前执行的 jar 文件)中正确读取文件

如果它不是当前运行的,只需用你的 jar 文件的路径更改可执行文件。

然后把filePath改成你想在jar文件里面使用的文件的路径。 IE。如果你的文件在

someJar.jar\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();

【讨论】:

【参考方案7】:

在你的技术之外,为什么不使用标准的 Java JarFile class 来获取你想要的引用呢?从那里你的大部分问题应该会消失。

【讨论】:

【参考方案8】:

如果您广泛使用资源,您可能会考虑使用 Commons VFS.

还支持: * 本地文件 * FTP、SFTP * HTTP 和 HTTPS *临时文件“支持正常的FS) * Zip、Jar 和 Tar(未压缩、tgz 或 tbz2) * gzip 和 bzip2 * 资源 * ram - “ramdrive” * 哑剧

还有JBoss VFS - 但没有太多记录。

【讨论】:

【参考方案9】:

我有 2 个用于读取数据的 CSV 文件。 java 程序导出为可运行的 jar 文件。当你导出它时,你会发现它不会用它导出你的资源。

我在 eclipse 中的项目下添加了一个名为 data 的文件夹。在那个文件夹中,我存储了我的 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 jar 文件中读取资源文件?的主要内容,如果未能解决你的问题,请参考以下文章

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

(转)java 从jar包中读取资源文件

怎么从外部读取jar包中的资源文件

Java从jar资源中读取文件

docker上的Spring启动无法从资源中读取属性文件,但可以在没有Docker的情况下使用java -jar

从 jar 中读取资源文件