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

我试图从一个单独的jar作为桌面应用程序来访问一个jar文件中的XML文件。 我可以得到我需要的文件的URL,但是当我将它传递给FileReader(作为string)时,我得到一个FileNotFoundException,说“文件名称,目录名称或卷标语法不正确。

作为一个参考点,我从同一个jar中读取图像资源并将其传递给一个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文件有什么问题?

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

http://java.sun.com/javase/6/docs/api/java/lang/Class.html#getResourceAsStream(java.lang.String);

你不会说如果这是一个桌面或Web应用程序。 如果是桌面,我将使用适当的ClassLoader的getResourceAsStream()方法,如果是Web应用程序,则使用Context。

它看起来好像使用URL.toString结果作为FileReader构造函数的参数。 URL.toString有点坏,而应该使用url.toURI().toString() 。 在任何情况下,string都不是文件path。

相反,你应该:

  • URL传递给ServicesLoader并让它调用openStream或类似的。
  • 使用Class.getResourceAsStream ,只是传递stream,可能在一个InputSource 。 (请记住检查是否为空,因为API太麻烦了。)

问题是我在调用XMLReader的parsing方法方面做得太过分了。 parsing方法接受一个I​​nputSource,所以没有理由甚至使用FileReader。 将上面的代码的最后一行更改为

 xr.parse( new InputSource( filename )); 

工作得很好。

我想指出的是,如果相同的资源在多个jar文件中,则是一个问题。 假设您想要读取/org/node/foo.txt,但不是从一个文件读取,而是从每个jar文件读取。

之前我已经遇到过几次同样的问题。 我希望在JDK 7中有人会写一个类path文件系统,但还没有。

Spring有Resource类,它可以很好地加载classpath资源。

我写了一个小原型来解决这个从多个jar文件中读取资源的问题。 原型不处理每个边界情况,但它处理在jar文件中的目录中查找资源。

我已经使用堆栈溢出了相当长的一段时间。 这是我记得回答一个问题的第二个答案,所以如果我拖得太久,就原谅我(这是我的本性)。

这是一个原型资源阅读器。 原型没有可靠的错误检查。

我有两个原型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(); } } } } }

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 JarFile类来获得你想要的引用呢? 从那里你的大部分问题应该消失。

如果您广泛使用资源,则可以考虑使用Commons VFS 。

还支持:*本地文件* FTP,SFTP * HTTP和HTTPS *临时文件“普通FS支持)* Zip,Jar和Tar(未压缩,tgz或tbz2)* gzip和bzip2 *资源* ram – ”ramdrive“* mime

还有JBoss VFS – 但没有多less文档。

我有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文件所在的位置。