JavaのクラスパスからリソースをロードするためのURL


197

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を指定できるすべての場所でこれを使用できるようにしたいと思います。

回答:


348

イントロと基本的な実装

まず最初に、少なくともURLStreamHandlerが必要になります。これにより、特定のURLへの接続が実際に開かれます。これは単に呼び出されることに注意してくださいHandler。これにより、指定が可能になりjava -Djava.protocol.handler.pkgs=org.my.protocols、サポートされるプロトコルとして「単純な」パッケージ名(この場合は「クラスパス」)を使用して自動的に選択されます。

使用法

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のように開き、私のオプションを維持したいどこかであなたを得るために打ち上げでプロパティ設定中に依存したくない、私はこのすべてを必要とします)。

回避策/拡張機能

手動コードハンドラー仕様

コードを制御すれば、

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

これは、ハンドラーを使用して接続を開きます。

しかし、繰り返しますが、これを行うにはURLが必要ないため、これは満足のいくものではありません。これは、制御できない(または制御したくない)libが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ごとに1回だけ呼び出すことができ、Tomcatはこのメソッドを使用してJNDIハンドラー(AFAIK)を登録することに注意してください。Jettyを試してください(私がします)。最悪の場合、最初にメソッドを使用してから、回避する必要があります。

ライセンス

私はこれをパブリックドメインにリリースします。変更したい場合は、どこかでOSSプロジェクトを開始し、詳細をここにコメントしてください。より適切な実装は、URLStreamHandlerFactoryを使用してごとにThreadLocalを格納することです。私もあなたに私の変更とテストクラスを与えます。URLStreamHandlerThread.currentThread().getContextClassLoader()


1
@Stephenこれはまさに私が探しているものです。更新内容を教えてください。com.github.fommil.common-utilsSonatypeを介してすぐに更新およびリリースする予定のパッケージの一部として含めることができます。
fommil 2013

5
を使用System.setProperty()してプロトコルを登録することもできます。いいねSystem.setProperty("java.protocol.handler.pkgs", "org.my.protocols");
tsauerwein 2014年

:Javaの9+は簡単に方法があるstackoverflow.com/a/56088592/511976
mhvelplund

100
URL url = getClass().getClassLoader().getResource("someresource.xxx");

それでうまくいくはずです。


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

3
-1 Thiloが指摘したように、これはOPが検討して拒否したものです。
sleske 2011

13
getResourceとgetResourceAsStreamは異なるメソッドです。getResourceAsStreamはAPIに適合しないことに同意しましたが、getResourceは、OPが要求したとおりのURLを返します。
romacafe

@romacafe:はい、そうです。これは優れた代替ソリューションです。
sleske 2011

2
OPはプロパティファイルソリューションを求めましたが、質問のタイトルのために他の人もここに来ました。そして、彼らはこの動的なソリューションを気に入っています:)
Jarekczek

14

私はこれはそれ自身の答えの価値があると思います-あなたがSpringを使っているなら、あなたはすでにこれを持っています

Resource firstResource =
    context.getResource("http://www.google.fi/");
Resource anotherResource =
    context.getResource("classpath:some/resource/path/myTemplate.txt");

春のドキュメントで説明され、スカフマンのコメントで指摘されたように。


IMHOスプリングの方ResourceLoader.getResource()がタスクに適しています(ApplicationContext.getResource()
内部的に

11

起動時にプログラムでプロパティを設定することもできます。

final String key = "java.protocol.handler.pkgs";
String newValue = "org.my.protocols";
if (System.getProperty(key) != null) {
    final String previousValue = System.getProperty(key);
    newValue += "|" + previousValue;
}
System.setProperty(key, newValue);

このクラスを使用する:

package org.my.protocols.classpath;

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

public class Handler extends URLStreamHandler {

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

したがって、これを行うための最も煩わしくない方法が得られます。:) java.net.URLは常にシステムプロパティの現在の値を使用します。


1
java.protocol.handler.pkgsシステム変数へのルックアップ用に追加のパッケージを追加するコードは、ハンドラーがなどの「既知の」プロトコルをまだ処理していない場合にのみ使用できますgopher://。意図は同じように、「人気」プロトコルを無効にする場合file://http://、それが通り、それを行うには遅すぎるかもしれないjava.net.URL#handlersマップがすでにそのプロトコルのための「標準」ハンドラを追加されます。したがって、唯一の方法は、この変数をJVMに渡すことです。
dma_k 2014年

6

Azderの回答に似ていますが、タクトが少し異なります。)

クラスパスからのコンテンツ用に事前定義されたプロトコルハンドラーがあるとは思いません。(いわゆるclasspath:プロトコル)。

ただし、Javaでは独自のプロトコルを追加できます。これは、具体的な実装を提供しjava.net.URLStreamHandlerjava.net.URLConnectionます。

この記事では、カスタムストリームハンドラーを実装する方法について説明します。 http://java.sun.com/developer/onlineTraining/protocolhandlers/


4
JVMに同梱されているプロトコルのリストを知っていますか?
ティロ

5

カスタムハンドラーのセットアップでのエラーを減らし、システムプロパティを利用するクラスを作成しました。そのため、最初にメソッドを呼び出したり、適切なコンテナー内にないことで問題が発生しません。問題が発生した場合の例外クラスもあります。

CustomURLScheme.java:
/*
 * The CustomURLScheme class has a static method for adding cutom protocol
 * handlers without getting bogged down with other class loaders and having to
 * call setURLStreamHandlerFactory before the next guy...
 */
package com.cybernostics.lib.net.customurl;

import java.net.URLStreamHandler;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Allows you to add your own URL handler without running into problems
 * of race conditions with setURLStream handler.
 * 
 * To add your custom protocol eg myprot://blahblah:
 * 
 * 1) Create a new protocol package which ends in myprot eg com.myfirm.protocols.myprot
 * 2) Create a subclass of URLStreamHandler called Handler in this package
 * 3) Before you use the protocol, call CustomURLScheme.add(com.myfirm.protocols.myprot.Handler.class);
 * @author jasonw
 */
public class CustomURLScheme
{

    // this is the package name required to implelent a Handler class
    private static Pattern packagePattern = Pattern.compile( "(.+\\.protocols)\\.[^\\.]+" );

    /**
     * Call this method with your handlerclass
     * @param handlerClass
     * @throws Exception 
     */
    public static void add( Class<? extends URLStreamHandler> handlerClass ) throws Exception
    {
        if ( handlerClass.getSimpleName().equals( "Handler" ) )
        {
            String pkgName = handlerClass.getPackage().getName();
            Matcher m = packagePattern.matcher( pkgName );

            if ( m.matches() )
            {
                String protocolPackage = m.group( 1 );
                add( protocolPackage );
            }
            else
            {
                throw new CustomURLHandlerException( "Your Handler class package must end in 'protocols.yourprotocolname' eg com.somefirm.blah.protocols.yourprotocol" );
            }

        }
        else
        {
            throw new CustomURLHandlerException( "Your handler class must be called 'Handler'" );
        }
    }

    private static void add( String handlerPackage )
    {
        // this property controls where java looks for
        // stream handlers - always uses current value.
        final String key = "java.protocol.handler.pkgs";

        String newValue = handlerPackage;
        if ( System.getProperty( key ) != null )
        {
            final String previousValue = System.getProperty( key );
            newValue += "|" + previousValue;
        }
        System.setProperty( key, newValue );
    }
}


CustomURLHandlerException.java:
/*
 * Exception if you get things mixed up creating a custom url protocol
 */
package com.cybernostics.lib.net.customurl;

/**
 *
 * @author jasonw
 */
public class CustomURLHandlerException extends Exception
{

    public CustomURLHandlerException(String msg )
    {
        super( msg );
    }

}

5

@Stephen https://stackoverflow.com/a/1769454/980442 およびhttp://docstore.mik.ua/orelly/java/exp/ch09_06.htmによるインスパイア

使用するには

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

このクラスをsun.net.www.protocol.classpathパッケージに作成してOracle JVM実装で実行するだけで、魅力のように機能します。

package sun.net.www.protocol.classpath;

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

public class Handler extends URLStreamHandler {

    @Override
    protected URLConnection openConnection(URL u) throws IOException {
        return Thread.currentThread().getContextClassLoader().getResource(u.getPath()).openConnection();
    }
}

別のJVM実装を使用している場合は、java.protocol.handler.pkgs=sun.net.www.protocolシステムプロパティを設定します。

参考までに:http ://docs.oracle.com/javase/7/docs/api/java/net/URL.html#URL(java.lang.String,%20java.lang.String,%20int,%20java.lang 。ストリング)


3

もちろん、URLStreamHandlerを登録するソリューションが最も適切ですが、最も単純なソリューションが必要になる場合もあります。だから、私はそのために次の方法を使用します:

/**
 * Opens a local file or remote resource represented by given path.
 * Supports protocols:
 * <ul>
 * <li>"file": file:///path/to/file/in/filesystem</li>
 * <li>"http" or "https": http://host/path/to/resource - gzipped resources are supported also</li>
 * <li>"classpath": classpath:path/to/resource</li>
 * </ul>
 *
 * @param path An URI-formatted path that points to resource to be loaded
 * @return Appropriate implementation of {@link InputStream}
 * @throws IOException in any case is stream cannot be opened
 */
public static InputStream getInputStreamFromPath(String path) throws IOException {
    InputStream is;
    String protocol = path.replaceFirst("^(\\w+):.+$", "$1").toLowerCase();
    switch (protocol) {
        case "http":
        case "https":
            HttpURLConnection connection = (HttpURLConnection) new URL(path).openConnection();
            int code = connection.getResponseCode();
            if (code >= 400) throw new IOException("Server returned error code #" + code);
            is = connection.getInputStream();
            String contentEncoding = connection.getContentEncoding();
            if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip"))
                is = new GZIPInputStream(is);
            break;
        case "file":
            is = new URL(path).openStream();
            break;
        case "classpath":
            is = Thread.currentThread().getContextClassLoader().getResourceAsStream(path.replaceFirst("^\\w+:", ""));
            break;
        default:
            throw new IOException("Missed or unsupported protocol in path '" + path + "'");
    }
    return is;
}

3

Java 9以降では、新しいを定義できますURLStreamHandlerProvider。のURLクラスは、実行時にそれをロードするために、サービスローダーのフレームワークを使用しています。

プロバイダーを作成します。

package org.example;

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

public class ClasspathURLStreamHandlerProvider extends URLStreamHandlerProvider {

    @Override
    public URLStreamHandler createURLStreamHandler(String protocol) {
        if ("classpath".equals(protocol)) {
            return new URLStreamHandler() {
                @Override
                protected URLConnection openConnection(URL u) throws IOException {
                    return ClassLoader.getSystemClassLoader().getResource(u.getPath()).openConnection();
                }
            };
        }
        return null;
    }

}

次の内容java.net.spi.URLStreamHandlerProviderMETA-INF/servicesディレクトリに呼び出されるファイルを作成します。

org.example.ClasspathURLStreamHandlerProvider

これで、次のようなURLクラスでプロバイダーが使用されます。

URL url = new URL("classpath:myfile.txt");

2

既にあるかどうかはわかりませんが、自分で簡単に作成できます。

この異なるプロトコルの例は、ファサードパターンのように見えます。ケースごとに異なる実装がある場合、共通のインターフェースがあります。

同じ原則を使用して、プロパティファイルから文字列を取得し、独自のカスタムプロトコルをチェックするResourceLoaderクラスを作成できます。

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

文字列の先頭からmyprotocol:を取り除き、リソースをロードする方法を決定し、リソースを提供します。


これは、サードパーティのlibがURLを使用することを望み、特定のプロトコルのリソースの解決を処理したい場合には機能しません。
mP。

2

Dilumsの答えの拡張:

コードを変更せずに、おそらくDilumが推奨するように、URL関連のインターフェースのカスタム実装を追求する必要があります。わかりやすくするために、Spring Frameworkのリソースのソースを確認することをお勧めします。コードはストリームハンドラーの形式ではありませんが、ユーザーが望んでいることを正確に実行するように設計されており、ASL 2.0ライセンスに基づいており、十分に信用してコードで再利用するのに十分フレンドリーです。


あなたが参照するそのページは、「クラスパスから、またはServletContextから取得する必要があるリソースにアクセスするために使用できる標準化されたURL実装は存在しない」と述べています。
ティロ

@ホームレス:ハングアップします、若者。もう少し経験があれば、すぐにコメントを投稿できます。
Cuga、

1

Spring Bootアプリでは、次を使用してファイルのURLを取得しました。

Thread.currentThread().getContextClassLoader().getResource("PromotionalOfferIdServiceV2.wsdl")

1

クラスパスにTomcatがある場合は、次のように簡単です。

TomcatURLStreamHandlerFactory.register();

これにより、「war」および「classpath」プロトコルのハンドラーが登録されます。


0

私はURLクラスを避けようと試み、代わりにに依存していURIます。したがって、URLSpringを使用しないルックアップなど、Spring Resourceを実行する必要がある場所では、次のようにします。

public static URL toURL(URI u, ClassLoader loader) throws MalformedURLException {
    if ("classpath".equals(u.getScheme())) {
        String path = u.getPath();
        if (path.startsWith("/")){
            path = path.substring("/".length());
        }
        return loader.getResource(path);
    }
    else if (u.getScheme() == null && u.getPath() != null) {
        //Assume that its a file.
        return new File(u.getPath()).toURI().toURL();
    }
    else {
        return u.toURL();
    }
}

URIを作成するには、を使用できますURI.create(..)。この方法は、ClassLoaderリソースの検索を行う。

スキームを検出するためにURLを文字列として解析しようとする他のいくつかの回答に気づきました。URIを渡し、代わりにそれを使用して解析する方が良いと思います。

Spring Sourceがリソースコードを分離するように頼んでcore、他のすべてのSpringのものが必要ないように、実際に問題を提出しました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.