Как мне прочитать файл ресурса из файла 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)
, увидеть
Вы не говорите, если это настольное или веб-приложение. Я бы использовал 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-файла.
Если вы интенсивно используете ресурсы, вы можете рассмотреть возможность использования Commons VFS.
Также поддерживаются: * Локальные файлы * FTP, SFTP * HTTP и HTTPS * Временные файлы "с обычной FS-поддержкой) * Zip, Jar и Tar (без сжатия, tgz или tbz2) * gzip и bzip2 * ресурсы * ram - "ramdrive" * mime
Есть также JBoss VFS - но это не очень задокументировано.