URL para cargar recursos desde classpath en Java


En Java, puede cargar todo tipo de recursos utilizando la misma API pero con diferentes protocolos de URL:

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

Esto desacopla muy bien la carga real del recurso de la aplicación que necesita el recurso, y como una URL es solo una cadena, la carga de recursos también es muy fácilmente configurable.

¿Hay un protocolo para cargar recursos usando el classloader actual? Esto es similar al protocolo Jar, excepto que no necesito saber qué archivo jar o carpeta de clase el recurso viene de.

Puedo hacer eso usando Class.getResourceAsStream("a.xml"), por supuesto, pero eso me requeriría usar una API diferente y, por lo tanto, cambiar el código existente. Quiero poder usar esto en todos los lugares donde pueda especificar una URL para el recurso ya, simplemente actualizando un archivo de propiedad.

Author: Pacerier, 2009-05-14

12 answers

Introducción e Implementación básica

Primero, vas a necesitar al menos un URLStreamHandler. Esto realmente abrirá la conexión a una URL dada. Observe que esto se llama simplemente Handler; esto le permite especificar java -Djava.protocol.handler.pkgs=org.my.protocols y se recogerá automáticamente, utilizando el nombre de paquete "simple" como protocolo soportado (en este caso "classpath").

Uso

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

Código

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

Problemas de lanzamiento

Si eres como yo, no quiero confiar en una propiedad que se establece en el lanzamiento para llegar a alguna parte (en mi caso, me gusta mantener mis opciones abiertas como Java WebStart - que es la razón I necesito todo esto).

Soluciones / Mejoras

Especificación del controlador de código manual

Si controlas el código, puedes hacer

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

Y esto usará su controlador para abrir la conexión.

Pero de nuevo, esto es menos que satisfactorio, ya que no necesita una URL para hacer esto, quiere hacerlo esto se debe a que alguna lib que no puedes (o no quieres) controlar quiere URL...

Registro del controlador JVM

La última opción es registrar un URLStreamHandlerFactory que manejará todas las URL a través de la jvm:

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

Para registrar el controlador, llame a URL.setURLStreamHandlerFactory() con su fábrica configurada. Entonces haz new URL("classpath:org/my/package/resource.extension") como el primer ejemplo y allá vas.

JVM Handler Registration Issue

Tenga en cuenta que este método solo se puede llamar una vez por JVM, y tenga en cuenta que Tomcat utilizará este método para registrar un controlador JNDI (AFAIK). Prueba Jetty( lo seré); en el peor de los casos, puedes usar el método primero y luego tiene que funcionar a tu alrededor.

Licencia

Libero esto al dominio público, y le pido que si desea modificar que inicie un proyecto de OSS en algún lugar y comente aquí con los detalles. Una mejor aplicación sería tener un URLStreamHandlerFactory que utiliza ThreadLocals para almacenar URLStreamHandlers para cada Thread.currentThread().getContextClassLoader(). Incluso te daré mis modificaciones y pruebas clase.

 325
Author: Stephen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-17 16:18:23
URL url = getClass().getClassLoader().getResource("someresource.xxx");

Eso debería bastar.

 85
Author: Rasin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-07 12:38:40

Creo que esto vale su propia respuesta: si estás usando Spring, ya lo tienes con

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

Como se explica en la documentación de la primavera y se señala en los comentarios de skaffman.

 10
Author: eis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-14 14:40:51

También puede establecer la propiedad mediante programación durante el inicio:

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

Usando esta clase:

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

Así se obtiene la forma menos intrusiva de hacer esto. :) de java.net.URL siempre utilizará el valor actual de las propiedades del sistema.

 9
Author: subes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-24 16:44:41

He creado una clase que ayuda a reducir los errores en la configuración de manejadores personalizados y aprovecha la propiedad del sistema para que no haya problemas con llamar a un método primero o no estar en el contenedor correcto. También hay una clase de excepción si te equivocas:

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
Author: Jason Wraxall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-11 23:36:23

(Similar a la respuesta de Azder, pero con un tacto ligeramente diferente.)

No creo que haya un manejador de protocolo predefinido para el contenido del classpath. (El llamado protocolo classpath:).

Sin embargo, Java le permite agregar sus propios protocolos. Esto se hace a través de implementaciones concretas java.net.URLStreamHandler y java.net.URLConnection.

Este artículo describe cómo un manejador de flujo personalizado puede ser aplicado: http://java.sun.com/developer/onlineTraining/protocolhandlers/.

 4
Author: Dilum Ranatunga,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:02:56

La solución con el registro de URLStreamHandlers es la más correcta, por supuesto, pero a veces se necesita la solución más simple. Por lo tanto, utilizo el siguiente método para eso:

/**
 * 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
Author: domax,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-13 19:15:08

Inspire por @Stephen https://stackoverflow.com/a/1769454/980442 y http://docstore.mik.ua/orelly/java/exp/ch09_06.htm

Utilizar

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

Simplemente cree esta clase en el paquete sun.net.www.protocol.classpath y ejecútela en la implementación de Oracle JVM para que funcione como un encanto.

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

En caso de que esté utilizando otra implementación JVM, establezca la propiedad del sistema java.protocol.handler.pkgs=sun.net.www.protocol.

FYI: http://docs.oracle.com/javase/7/docs/api/java/net/URL.html#URL(java.lang.String,%20java.lang.String,%20int,%20java.lang.String)

 3
Author: Daniel De León,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:55:10

Una extensión de La respuesta de Dilums :

Sin cambiar el código, es probable que necesite implementar implementaciones personalizadas de interfaces relacionadas con URL como recomienda Dilum. Para simplificar las cosas para usted, puedo recomendar mirar el origen de Recursos de Spring Framework . Si bien el código no está en la forma de un controlador de flujo, ha sido diseñado para hacer exactamente lo que está buscando hacer y está bajo la licencia ASL 2.0, lo que lo hace lo suficientemente amigable para su reutilización en su código con due crédito.

 2
Author: DavidValeri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:25

No sé si ya hay uno, pero puedes hacerlo tú mismo fácilmente.

Ese ejemplo de protocolos diferentes me parece un patrón de fachada. Tiene una interfaz común cuando hay diferentes implementaciones para cada caso.

Podría usar el mismo principio, hacer una clase ResourceLoader que tome la cadena de su archivo de propiedades, y verifique un protocolo personalizado nuestro

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

Tira el myprotocol: desde el inicio de la cadena y luego hace un decisión de qué manera cargar el recurso, y solo le da el recurso.

 1
Author: Azder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-05-14 04:16:13

Trato de evitar la clase URL y en su lugar me baso en URI. Por lo tanto, para las cosas que necesitan URL donde me gustaría hacer Recurso de Primavera como buscar con out Spring hago lo siguiente:

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

Para crear un URI puede usar URI.create(..). De esta manera también es mejor porque usted controla el ClassLoader que hará la búsqueda de recursos.

Noté algunas otras respuestas tratando de analizar la URL como una cadena para detectar el esquema. Creo que es mejor pasar URI y usarlo para analizar en su lugar.

De hecho, he presentado un problema hace un tiempo con Spring Source rogándoles que separen su código de recurso de core para que no necesite todas las otras cosas de Spring.

 0
Author: Adam Gent,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-29 19:36:09

En una aplicación de Spring Boot, usé lo siguiente para obtener la URL del archivo,

Thread.currentThread().getContextClassLoader().getResource("PromotionalOfferIdServiceV2.wsdl")
 0
Author: Sathesh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-01 05:31:50