it-swarm.com.de

URL zum Laden von Ressourcen aus dem Klassenpfad in Java

In Java können Sie alle Arten von Ressourcen mit derselben API, jedoch mit unterschiedlichen URL-Protokollen laden:

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

Dies entkoppelt schön das tatsächliche Laden der Ressource von der Anwendung, die die Ressource benötigt. Da es sich bei einer URL nur um einen String handelt, ist das Laden der Ressource auch sehr einfach konfigurierbar.

Gibt es ein Protokoll zum Laden von Ressourcen mit dem aktuellen Classloader? Dies ähnelt dem Jar-Protokoll, außer dass ich nicht wissen muss, aus welcher JAR-Datei oder welchem ​​Klassenordner die Ressource stammt.

Ich kann das natürlich mit Class.getResourceAsStream("a.xml") machen, aber dazu müsste ich eine andere API verwenden und somit den vorhandenen Code ändern. Ich möchte dies an allen Stellen verwenden können, an denen ich bereits eine URL für die Ressource angeben kann, indem ich einfach eine Eigenschaftsdatei aktualisiert.

182
Thilo

Intro und grundlegende Implementierung

Zunächst benötigen Sie mindestens einen URLStreamHandler. Dadurch wird tatsächlich die Verbindung zu einer bestimmten URL geöffnet. Beachten Sie, dass dies einfach als Handler bezeichnet wird. Damit können Sie Java -Djava.protocol.handler.pkgs=org.my.protocols angeben und es wird automatisch abgeholt, wobei der "einfache" Paketname als unterstütztes Protokoll (in diesem Fall "classpath") verwendet wird.

Verwendungszweck

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

Code

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();
    }
}

Probleme starten

Wenn Sie wie ich sind, möchten Sie sich nicht darauf verlassen, dass eine Eigenschaft beim Start festgelegt wird, um Sie irgendwo hin zu bringen (in meinem Fall möchte ich meine Optionen wie Java WebStart offen halten. Deshalb I brauche das alles).

Problemumgehungen/Verbesserungen

Handbuch Code Handler-Spezifikation

Wenn Sie den Code kontrollieren, können Sie dies tun

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

und dies wird Ihren Handler verwenden, um die Verbindung zu öffnen.

Dies ist jedoch weniger als zufriedenstellend, da Sie dazu keine URL benötigen - Sie möchten dies tun, weil eine Lib, die Sie nicht steuern können (oder wollen), URLs benötigt ...

JVM-Handler-Registrierung

Die ultimative Option ist die Registrierung einer URLStreamHandlerFactory, die alle URLs im jvm behandelt:

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);
    }
}

Um den Handler zu registrieren, rufen Sie URL.setURLStreamHandlerFactory() mit Ihrer konfigurierten Factory auf. Dann new URL("classpath:org/my/package/resource.extension") wie im ersten Beispiel und los geht's.

JVM-Handler-Registrierungsproblem

Beachten Sie, dass diese Methode nur einmal pro JVM aufgerufen werden kann. Beachten Sie, dass Tomcat diese Methode zum Registrieren eines JNDI-Handlers (AFAIK) verwendet. Versuchen Sie es mit Jetty (ich werde sein); Im schlimmsten Fall können Sie die Methode zuerst verwenden und dann muss sie um Sie herum arbeiten!

Lizenz

Ich mache das öffentlich zugänglich, und bitte Sie, wenn Sie ändern möchten, dass Sie ein OSS-Projekt irgendwo starten und hier mit den Details kommentieren. Eine bessere Implementierung wäre eine URLStreamHandlerFactory, die ThreadLocals verwendet, um URLStreamHandlers für jede Thread.currentThread().getContextClassLoader() zu speichern. Ich gebe dir sogar meine Modifikationen und Testkurse.

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

Das sollte es tun. 

88
Rasin

Ich denke, das ist eine eigene Antwort wert - wenn Sie Spring verwenden, haben Sie dies bereits mit

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

Wie in der Spring-Dokumentation erklärt und in den Kommentaren von Skaffman hervorgehoben.

11
eis

Sie können die Eigenschaft auch während des Startvorgangs programmgesteuert festlegen:

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);

Verwendung dieser Klasse:

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();
    }
}

Auf diese Weise erhalten Sie den am wenigsten aufdringlichen Weg. :) Java.net.URL verwendet immer den aktuellen Wert aus den Systemeigenschaften.

9
subes

Ich habe eine Klasse erstellt, die beim Reduzieren von Fehlern beim Einrichten benutzerdefinierter Handler hilft und die Systemeigenschaften nutzt, sodass es keine Probleme gibt, wenn eine Methode zuerst aufgerufen wird oder nicht im richtigen Container ist. Es gibt auch eine Ausnahmeklasse, wenn Sie etwas falsch verstehen:

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 );
    }

}
4
Jason Wraxall

(Ähnlich wie Antwort von Azder , aber ein etwas anderer Takt.)

Ich glaube nicht, dass es einen vordefinierten Protokoll-Handler für Inhalte aus dem Klassenpfad gibt. (Das sogenannte classpath:-Protokoll).

In Java können Sie jedoch eigene Protokolle hinzufügen. Dies geschieht durch die Bereitstellung konkreter Implementierungen Java.net.URLStreamHandler und Java.net.URLConnection .

Dieser Artikel beschreibt, wie ein benutzerdefinierter Stream-Handler implementiert werden kann:  http://Java.Sun.com/developer/onlineTraining/protocolhandlers/.

4
Dilum Ranatunga

Inspire by @Stephen https://stackoverflow.com/a/1769454/980442 Und http://docstore.mik.ua/orelly/Java/exp/ch09_06.htm

Benutzen 

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

erstellen Sie diese Klasse einfach in ein Sun.net.www.protocol.classpath-Paket und führen Sie sie in der Oracle JVM-Implementierung aus, um wie ein Zauber zu wirken.

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();
    }
}

Wenn Sie eine andere JVM-Implementierung verwenden, legen Sie die Systemeigenschaft Java.protocol.handler.pkgs=Sun.net.www.protocol fest.

Zu Ihrer Information: http://docs.Oracle.com/javase/7/docs/api/Java/net/URL.html#URL(Java.lang.String,%20Java.lang.String,%20int ,% 20Java.lang.String)

3
Daniel De León

Eine Erweiterung zu Dilums Antwort :

Wenn Sie den Code nicht ändern, müssen Sie wahrscheinlich benutzerdefinierte Implementierungen von URL-bezogenen Schnittstellen verfolgen, wie von Dilum empfohlen. Um die Dinge für Sie zu vereinfachen, kann ich empfehlen, die Quelle für Spring Frameworks Resources zu suchen. Obwohl der Code nicht in Form eines Stream-Handlers vorliegt, wurde er so konzipiert, dass er genau das tut, was Sie suchen, und unter der ASL 2.0-Lizenz steht. Dadurch ist er für die Wiederverwendung in Ihrem Code geeignet.

2
DavidValeri

Die Lösung mit der Registrierung von URLStreamHandlers ist natürlich am korrektesten, aber manchmal ist die einfachste Lösung erforderlich. Ich verwende dafür die folgende Methode:

/**
 * 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;
}
2
domax

Ab Java 9+ können Sie eine neue URLStreamHandlerProvider definieren. Die Klasse URL verwendet das Service Loader Framework, um es zur Laufzeit zu laden.

Erstellen Sie einen Anbieter:

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;
    }

}

Erstellen Sie im Verzeichnis Java.net.spi.URLStreamHandlerProvider eine Datei mit dem Namen META-INF/services mit folgendem Inhalt:

org.example.ClasspathURLStreamHandlerProvider

Jetzt verwendet die URL-Klasse den Provider, wenn sie Folgendes sieht:

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

Ich weiß nicht, ob es schon einen gibt, aber Sie können es sich leicht selbst machen.

Dieses unterschiedliche Protokollbeispiel sieht für mich wie ein Fassadenmuster aus. Sie haben eine gemeinsame Schnittstelle, wenn für jeden Fall unterschiedliche Implementierungen vorhanden sind.

Sie können dasselbe Prinzip verwenden, eine ResourceLoader-Klasse erstellen, die die Zeichenfolge aus Ihrer Eigenschaftendatei entnimmt und nach einem benutzerdefinierten Protokoll von uns sucht

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

entfernt das myprotocol: vom Anfang des Strings und entscheidet dann, auf welche Weise die Ressource geladen werden soll, und gibt nur die Ressource an.

1
Azder

Wenn Sie Tomcat im Klassenpfad haben, ist es so einfach wie folgt:

TomcatURLStreamHandlerFactory.register();

Dadurch werden Handler für die Protokolle "Krieg" und "Klassenpfad" registriert.

1

Ich versuche die URL-Klasse zu vermeiden und verlasse mich stattdessen auf URI. Also für Dinge, die URL brauchen, wo ich Spring Resource wie Lookup ohne Spring machen möchte, mache ich Folgendes:

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();
    }
}

Um einen URI zu erstellen, können Sie URI.create(..) verwenden. Dieser Weg ist auch besser, weil Sie die ClassLoader steuern, die die Ressourcensuche durchführt.

Ich bemerkte einige andere Antworten, die versuchten, die URL als Zeichenfolge zu analysieren, um das Schema zu ermitteln. Ich denke, es ist besser, um URI herumzugehen und es stattdessen zu analysieren.

Ich habe vor einiger Zeit ein Problem mit Spring Source eingereicht, in dem sie darum gebeten werden, ihren Ressourcencode von core zu trennen, damit Sie nicht alle anderen Spring-Sachen benötigen.

0
Adam Gent

In einer Spring Boot-App habe ich Folgendes verwendet, um die Datei-URL abzurufen:

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