1. ホーム

[解決済み】Javaでクラスパスからリソースを読み込むためのURL

2022-04-12 12:56:26

質問

Javaでは、同じAPIを使用しても、異なるURLプロトコルであらゆる種類のリソースを読み込むことができます。

file:///tmp.txt
http://127.0.0.1:8080/a.properties
jar:http://www.foo.com/bar/baz.jar!/COM/foo/Quux.class

これは、リソースの実際の読み込みと、リソースを必要とするアプリケーションとをうまく切り離しています。また、URLは単なる文字列なので、リソースの読み込みも非常に簡単に設定することができます。

現在のクラスローダーを使用してリソースをロードするためのプロトコルはありますか? これは、リソースがどのjarファイルまたはクラスフォルダから来るかを知る必要がないことを除けば、Jarプロトコルに似ています。

を使えばできるんです。 Class.getResourceAsStream("a.xml") もちろん、別の API を使用する必要があり、既存のコードに変更を加える必要があります。私は、プロパティ・ファイルを更新するだけで、すでにリソースの URL を指定できるすべての場所でこれを使用できるようにしたいのです。

どのように解決するのですか?

導入と基本的な実装

まず最初に、最低でもURLStreamHandlerが必要です。 これは実際に与えられたURLへの接続を開くものです。 これは単に Handler を指定することができます。 java -Djava.protocol.handler.pkgs=org.my.protocols と入力すると、サポートされているプロトコル(この場合は "classpath")として "simple"パッケージ名を使用して、自動的にピックアップされるようになっています。

使用方法

new URL("classpath:org/my/package/resource.extension").openConnection();

コード

package org.my.protocols.classpath;

import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;

/** A {@link URLStreamHandler} that handles resources on the classpath. */
public class Handler extends URLStreamHandler {
    /** The classloader to find resources from. */
    private final ClassLoader classLoader;

    public Handler() {
        this.classLoader = getClass().getClassLoader();
    }

    public Handler(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    @Override
    protected URLConnection openConnection(URL u) throws IOException {
        final URL resourceUrl = classLoader.getResource(u.getPath());
        return resourceUrl.openConnection();
    }
}

起動時の問題

私のような人は、起動時に設定されるプロパティに頼ってどこかに行くのは嫌でしょう(私の場合、Java WebStart のように選択肢を広げておきたいのです - だからこそ I がすべて必要です)。

回避策/強化策

マニュアルコード ハンドラ仕様

コードを制御する場合、以下のようなことが可能です。

new URL(null, "classpath:some/package/resource.extension", new org.my.protocols.classpath.Handler(ClassLoader.getSystemClassLoader()))

で、これは接続を開くためにあなたのハンドラを使用します。

しかし、これもまた満足のいくものではありません。なぜなら、これをするためにURLは必要ないからです。あなたがコントロールできない(あるいはしたくない)あるライブラリはURLを欲しているので、これをしたいのです...。

JVMハンドラの登録

究極のオプションは URLStreamHandlerFactory は、jvm 全体ですべての URL を処理します。

package my.org.url;

import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.util.HashMap;
import java.util.Map;

class ConfigurableStreamHandlerFactory implements URLStreamHandlerFactory {
    private final Map<String, URLStreamHandler> protocolHandlers;

    public ConfigurableStreamHandlerFactory(String protocol, URLStreamHandler urlHandler) {
        protocolHandlers = new HashMap<String, URLStreamHandler>();
        addHandler(protocol, urlHandler);
    }

    public void addHandler(String protocol, URLStreamHandler urlHandler) {
        protocolHandlers.put(protocol, urlHandler);
    }

    public URLStreamHandler createURLStreamHandler(String protocol) {
        return protocolHandlers.get(protocol);
    }
}

ハンドラを登録するには URL.setURLStreamHandlerFactory() を設定したファクトリーで実行します。 そして new URL("classpath:org/my/package/resource.extension") 最初の例と同じように、これで完了です。

JVMハンドラ登録の問題

このメソッドは、JVMごとに一度だけ呼び出すことができることに注意してください。また、TomcatはJNDIハンドラを登録するためにこのメソッドを使用することに注意してください(AFAIK)。 Jettyを試してみてください(私はそうします); 最悪の場合、最初にこのメソッドを使用することができ、その後、あなたの周りで動作する必要があります!

ライセンス

これをパブリックドメインとして公開しますが、改変する場合は、どこかでOSSプロジェクトを立ち上げ、その詳細をここにコメントしていただくようお願いします。 より良い実装としては URLStreamHandlerFactory を使用することで ThreadLocal を格納するために URLStreamHandler は、各 Thread.currentThread().getContextClassLoader() . 私の修正とテストクラスもあげます。