私はそのように私のjarファイル内からリソースを読みたいと思います:
File file;
file = new File(getClass().getResource("/file.txt").toURI());
BufferredReader reader = new BufferedReader(new FileReader(file));
//Read the file
eclipseで実行しても問題なく動作しますが、jarにエクスポートして実行すると、IllegalArgumentExceptionが発生します。
Exception in thread "Thread-2"
Java.lang.IllegalArgumentException: URI is not hierarchical
なぜかわかりませんが、いくつかのテストで変更した場合
file = new File(getClass().getResource("/file.txt").toURI());
に
file = new File(getClass().getResource("/folder/file.txt").toURI());
それは逆に動作します(jarでは動作しますがEclipseでは動作しません)。
私はEclipseを使用しています、そして私のファイルのあるフォルダーはクラスフォルダーの中にあります。
File としてリソースをアドレス指定しようとするのではなく、 ClassLoader に getStourceAsStream を介して InputStream を返すように依頼します。
InputStream in = getClass().getResourceAsStream("/file.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
file.txt
リソースがクラスパスで利用可能である限り、このアプローチはfile.txt
リソースがclasses/
ディレクトリ内にあるかjar
内にあるかにかかわらず同じように機能します。
URI is not hierarchical
は、jarファイル内のリソースのURIがfile:/example.jar!/file.txt
のようになるために発生します。 jar
(Zip
ファイル)内のエントリは、普通の File のように読むことはできません。
これは、以下の答えでよく説明されています。
Jar内のファイルにアクセスするには、2つの選択肢があります。
ファイルをパッケージ名と一致するディレクトリ構造に配置し(.jarファイルを抽出した後は、.classファイルと同じディレクトリにあるはずです)、getClass().getResourceAsStream("file.txt")
を使用してアクセスします
ファイルをルートに配置し(.jarファイルを抽出した後、ルートに配置します)、次にThread.currentThread().getContextClassLoader().getResourceAsStream("file.txt")
を使用してアクセスします。
Jarがプラグインとして使用されている場合、最初のオプションは機能しない可能性があります。
あなたがファイルとして読みたいならば、私はまだ同様の解決策があると思います:
ClassLoader classLoader = getClass().getClassLoader();
File file = new File(classLoader.getResource("file/test.xml").getFile());
私は前にこの問題を抱えていたと私はロードのための代替手段を作りました。基本的に最初の方法は.jarファイル内で動作し、2番目の方法はEclipseまたは他のIDE内で動作します。
public class MyClass {
public static InputStream accessFile() {
String resource = "my-file-located-in-resources.txt";
// this is the path within the jar file
InputStream input = MyClass.class.getResourceAsStream("/resources/" + resource);
if (input == null) {
// this is how we load file within editor (eg Eclipse)
input = MyClass.class.getClassLoader().getResourceAsStream(resource);
}
return input;
}
}
必ず正しい区切り文字で作業してください。相対パス内のすべての/
をFile.separator
に置き換えました。これはIDEではうまくいきましたが、ビルドJARではうまくいきませんでした。
今まで(2017年12月)、これが私が見つけた唯一の解決策であり、これは 両方 IDEの内側と外側の両方で動作します。
PathMatchingResourcePatternResolver を使用します。
注: spring-bootでも動作します
この例では、 src/main/resources/my_folder にあるいくつかのファイルを読んでいます。
try {
// Get all the files under this inner resource folder: my_folder
String scannedPackage = "my_folder/*";
PathMatchingResourcePatternResolver scanner = new PathMatchingResourcePatternResolver();
Resource[] resources = scanner.getResources(scannedPackage);
if (resources == null || resources.length == 0)
log.warn("Warning: could not find any resources in this scanned package: " + scannedPackage);
else {
for (Resource resource : resources) {
log.info(resource.getFilename());
// Read the file content (I used BufferedReader, but there are other solutions for that):
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resource.getInputStream()));
String line = null;
while ((line = bufferedReader.readLine()) != null) {
// ...
// ...
}
bufferedReader.close();
}
}
} catch (Exception e) {
throw new Exception("Failed to read the resources folder: " + e.getMessage(), e);
}
Javaについて詳しく調べた後、私にとってはうまくいくように思われる唯一の解決策は、開発環境(IDE)にいない限り、jarファイル自体を手動で読み取ることです。
/** @return The root folder or jar file that the class loader loaded from */
public static final File getClasspathFile() {
return new File(YourMainClass.class.getProtectionDomain().getCodeSource().getLocation().getFile());
}
/** @param resource The path to the resource
* @return An InputStream containing the resource's contents, or
* <b><code>null</code></b> if the resource does not exist */
public static final InputStream getResourceAsStream(String resource) {
resource = resource.startsWith("/") ? resource : "/" + resource;
if(getClasspathFile().isDirectory()) {//Development environment:
return YourMainClass.class.getResourceAsStream(resource);
}
final String res = resource;//Jar or exe:
return AccessController.doPrivileged(new PrivilegedAction<InputStream>() {
@SuppressWarnings("resource")
@Override
public InputStream run() {
try {
final JarFile jar = new JarFile(getClasspathFile());
String resource = res.startsWith("/") ? res.substring(1) : res;
if(resource.endsWith("/")) {//Directory; list direct contents:(Mimics normal getResourceAsStream("someFolder/") behaviour)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Enumeration<JarEntry> entries = jar.entries();
while(entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if(entry.getName().startsWith(resource) && entry.getName().length() > resource.length()) {
String name = entry.getName().substring(resource.length());
if(name.contains("/") ? (name.endsWith("/") && (name.indexOf("/") == name.lastIndexOf("/"))) : true) {//If it's a folder, we don't want the children's folders, only the parent folder's children!
name = name.endsWith("/") ? name.substring(0, name.length() - 1) : name;
baos.write(name.getBytes(StandardCharsets.UTF_8));
baos.write('\r');
baos.write('\n');
}
}
}
jar.close();
return new ByteArrayInputStream(baos.toByteArray());
}
JarEntry entry = jar.getJarEntry(resource);
InputStream in = entry != null ? jar.getInputStream(entry) : null;
if(in == null) {
jar.close();
return in;
}
final InputStream stream = in;//Don't manage 'jar' with try-with-resources or close jar until the
return new InputStream() {//returned stream is closed(closing the jar closes all associated InputStreams):
@Override
public int read() throws IOException {
return stream.read();
}
@Override
public int read(byte b[]) throws IOException {
return stream.read(b);
}
@Override
public int read(byte b[], int off, int len) throws IOException {
return stream.read(b, off, len);
}
@Override
public long skip(long n) throws IOException {
return stream.skip(n);
}
@Override
public int available() throws IOException {
return stream.available();
}
@Override
public void close() throws IOException {
try {
jar.close();
} catch(IOException ignored) {
}
stream.close();
}
@Override
public synchronized void mark(int readlimit) {
stream.mark(readlimit);
}
@Override
public synchronized void reset() throws IOException {
stream.reset();
}
@Override
public boolean markSupported() {
return stream.markSupported();
}
};
} catch(Throwable e) {
e.printStackTrace();
return null;
}
}
});
}
注: 上記のコードは、メインクラスにある場合にのみjarファイルに対して正しく機能するようです。その理由はわかりません。
以下のコードはSpring boot(kotlin)で動作します:
val authReader = InputStreamReader(javaClass.getResourceAsStream("/file1.json"))
Springを使用している場合は、次の方法を使用してsrc/main/resourcesからファイルを読み取ることができます。
import Java.io.BufferedReader;
import Java.io.IOException;
import Java.io.InputStream;
import Java.io.InputStreamReader;
import org.springframework.core.io.ClassPathResource;
public String readFileToString(String path) throws IOException {
StringBuilder resultBuilder = new StringBuilder("");
ClassPathResource resource = new ClassPathResource(path);
try (
InputStream inputStream = resource.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
resultBuilder.append(line);
}
}
return resultBuilder.toString();
}
これはJavaでも機能するはずです。私が使用している次のコードはkotlinを使用しています。
val resource = Thread.currentThread().contextClassLoader.getResource('resources.txt')
クラスパスからルートパスとして読み込むクラスローダーを使用できます(先頭に「/」はありません)
InputStream in = getClass().getClassLoader().getResourceAsStream("file.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
問題は、特定のサードパーティライブラリが入力ストリームではなくファイルパス名を必要とすることです。答えのほとんどはこの問題に対処していません。
この場合、1つの回避策はリソースの内容を一時ファイルにコピーすることです。次の例ではjUnitのTemporaryFolder
を使います。
private List<String> decomposePath(String path){
List<String> reversed = Lists.newArrayList();
File currFile = new File(path);
while(currFile != null){
reversed.add(currFile.getName());
currFile = currFile.getParentFile();
}
return Lists.reverse(reversed);
}
private String writeResourceToFile(String resourceName) throws IOException {
ClassLoader loader = getClass().getClassLoader();
InputStream configStream = loader.getResourceAsStream(resourceName);
List<String> pathComponents = decomposePath(resourceName);
folder.newFolder(pathComponents.subList(0, pathComponents.size() - 1).toArray(new String[0]));
File tmpFile = folder.newFile(resourceName);
Files.copy(configStream, tmpFile.toPath(), REPLACE_EXISTING);
return tmpFile.getAbsolutePath();
}
何らかの理由で、WebアプリケーションをWildFly 14にデプロイしたときにclassLoader.getResource()
が常にnullを返していました。getClass().getClassLoader()
またはThread.currentThread().getContextClassLoader()
からclassLoaderを取得すると、nullが返されます。
getClass().getClassLoader()
APIドキュメントによると、
「クラスのクラスローダを返します。ブートストラップクラスローダを表すためにnullを使用する実装もあります。このクラスがブートストラップクラスローダによってロードされた場合、このメソッドはnullを返します。」
あなたがWildFlyを使っていて、あなたのウェブアプリケーションがこれを試しているなら、そうかもしれません
request.getServletContext().getResource()
はリソースのURLを返しました。ここでのリクエストはServletRequestのオブジェクトです。