デスクトップアプリケーションとして実行されている別のjarから、jarファイル内のXMLファイルにアクセスしようとしています。必要なファイルのURLを取得できますが、それを(文字列として)FileReaderに渡すと、「ファイル名、ディレクトリ名、またはボリュームラベルの構文が正しくありません」というFileNotFoundExceptionが表示されます。
参考として、同じjarから画像リソースを読み取り、ImageIconコンストラクターにURLを渡すのに問題はありません。これは、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ファイルを読み取ることの何が問題になっていますか?
問題は、XMLReaderのparseメソッドを呼び出す際に私が一歩踏み出しすぎていたことです。 parseメソッドは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)
これがデスクトップアプリかWebアプリかはわかりません。デスクトップの場合は適切なClassLoaderのgetResourceAsStream()
メソッドを使用し、Webアプリの場合はContextを使用します。
FileReader
コンストラクターへの引数として_URL.toString
_の結果を使用しているように見えます。 _URL.toString
_は少し壊れているので、代わりに通常url.toURI().toString()
を使用する必要があります。いずれにしても、文字列はファイルパスではありません。
代わりに、次のいずれかを行う必要があります。
URL
をServicesLoader
に渡し、openStream
または同様のものを呼び出します。Class.getResourceAsStream
_を使用して、おそらくInputSource
内でストリームを渡します。 (APIは少し厄介なので、nullをチェックすることを忘れないでください。)1つの問題は、同じリソースが複数のjarファイルにある場合にどうなるかを指摘したいと思います。 /org/node/foo.txtを読みたいが、1つのファイルからではなく、すべてのjarファイルから読みたいとしましょう。
この同じ問題に何度か遭遇しました。 JDK 7では、誰かがクラスパスファイルシステムを記述することを望んでいましたが、残念ながらまだありません。
Springには、クラスパスリソースを非常にうまくロードできるResourceクラスがあります。
複数のjarファイルからリソースを読み取るというこの問題を解決するために、私は小さなプロトタイプを作成しました。プロトタイプはすべてのEdgeケースを処理するわけではありませんが、jarファイルにあるディレクトリ内のリソースの検索を処理します。
私はかなり以前からStack Overflowを使用しています。これは質問に答えるのを覚えている2番目の答えですので、長すぎても許してくれます(それが私の性質です)。
これはプロトタイプのリソースリーダーです。プロトタイプには、堅牢なエラーチェックがありません。
セットアップした2つのプロトタイプ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ファイルでない場合は、jarファイルのパスで実行可能ファイルを変更します。
次に、filePathをjarファイル内で使用するファイルのパスに変更します。 I.E.あなたのファイルが
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ファイルとしてエクスポートされます。エクスポートすると、リソースがエクスポートされないことがわかります。
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 JarFileクラス を使用して、必要な参照を取得してみませんか?そこからほとんどの問題は解決するはずです。
リソースを広範囲に使用する場合は、 Commons VFS の使用を検討してください。
*ローカルファイル* FTP、SFTP * HTTPおよびHTTPS *一時ファイル "normal FS backed)* Zip、JarおよびTar(非圧縮、tgzまたはtbz2)* gzipおよびbzip2 *リソース* ram-"ramdrive" * mime
JBoss VFS もありますが、あまり文書化されていません。