URL para carregar resources do caminho de class em Java

Em Java, você pode carregar todos os tipos de resources usando a mesma API, mas com 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 

Isso separa muito bem o carregamento real do recurso do aplicativo que precisa do recurso e, como uma URL é apenas uma String, o carregamento de resources também é muito facilmente configurável.

Existe um protocolo para carregar resources usando o classloader atual? Isso é semelhante ao protocolo Jar, exceto que não preciso saber de qual arquivo jar ou pasta de class o recurso está vindo.

Eu posso fazer isso usando Class.getResourceAsStream("a.xml") , é claro, mas isso exigiria que eu usasse uma API diferente e, portanto, as alterações no código existente. Eu quero ser capaz de usar isso em todos os lugares onde posso especificar uma URL para o recurso já, apenas atualizando um arquivo de propriedade.

Introdução e Implementação Básica

Primeiro, você precisará de pelo menos um URLStreamHandler. Isso realmente abrirá a conexão com um determinado URL. Observe que isso é simplesmente chamado de Handler ; isso permite que você especifique java -Djava.protocol.handler.pkgs=org.my.protocols e ele será automaticamente selecionado, usando o nome do pacote “simple” como o protocolo suportado (neste 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(); } } 

Questões de lançamento

Se você é como eu, não quer depender de uma propriedade que está sendo configurada no lançamento para levá-lo a algum lugar (no meu caso, eu gosto de manter minhas opções abertas como o Java WebStart – é por isso que eu preciso de tudo isso ).

Soluções alternativas / aprimoramentos

Especificação manual do manipulador de código

Se você controlar o código, você pode fazer

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

e isso usará seu manipulador para abrir a conexão.

Mas, novamente, isso é menos do que satisfatório, já que você não precisa de uma URL para fazer isso – você quer fazer isso porque algum lib que você não pode (ou não quer) controlar quer urls …

Registro do manipulador da JVM

A opção final é registrar um URLStreamHandlerFactory que manipulará todos os URLs do 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 protocolHandlers; public ConfigurableStreamHandlerFactory(String protocol, URLStreamHandler urlHandler) { protocolHandlers = new HashMap(); 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 o manipulador, chame URL.setURLStreamHandlerFactory() com sua fábrica configurada. Então faça o new URL("classpath:org/my/package/resource.extension") como o primeiro exemplo e você vai embora.

Problema de Registro do Manipulador da JVM

Observe que esse método pode ser chamado apenas uma vez por JVM e observe que o Tomcat usará esse método para registrar um manipulador JNDI (AFAIK). Tente Jetty (eu serei); Na pior das hipóteses, você pode usar o método primeiro e, em seguida, ele tem que trabalhar em torno de você!

Licença

Eu libero isso para o domínio público, e peço que se você deseja modificar que você inicie um projeto OSS em algum lugar e comente aqui com os detalhes. Uma implementação melhor seria ter um URLStreamHandlerFactory que usa ThreadLocal s para armazenar URLStreamHandler s para cada Thread.currentThread().getContextClassLoader() . Até te darei minhas modificações e aulas de teste.

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

Isso deve resolver.

Eu acho que isso vale a sua própria resposta – se você está usando Spring, você já tem isso com

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

Como explicado na documentação da primavera e apontado nos comentários por skaffman.

Você também pode definir a propriedade programaticamente durante a boot:

 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 class:

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

Assim, você obtém a maneira menos intrusiva de fazer isso. 🙂 java.net.URL sempre usará o valor atual das propriedades do sistema.

Eu criei uma class que ajuda a reduzir erros na configuração de manipuladores personalizados e aproveita a propriedade do sistema para que não haja problemas em chamar primeiro um método ou não estar no contêiner correto. Há também uma class de exceção, se você errar:

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

(Semelhante à resposta de Azder , mas um pouco diferente.)

Não acredito que exista um manipulador de protocolo predefinido para o conteúdo do caminho de class. (O chamado classpath: protocolo).

No entanto, o Java permite que você adicione seus próprios protocolos. Isso é feito através do fornecimento de implementações concretas java.net.URLStreamHandler e java.net.URLConnection .

Este artigo descreve como um manipulador de stream personalizado pode ser implementado: http://java.sun.com/developer/onlineTraining/protocolhandlers/ .

A solução com registro de URLStreamHandlers é a mais correta, é claro, mas às vezes a solução mais simples é necessária. Então, eu uso o seguinte método para isso:

 /** * Opens a local file or remote resource represented by given path. * Supports protocols: * 
    *
  • "file": file:///path/to/file/in/filesystem
  • *
  • "http" or "https": http://host/path/to/resource - gzipped resources are supported also
  • *
  • "classpath": classpath:path/to/resource
  • *
* * @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; }

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

Usar

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

basta criar essa class no pacote sun.net.www.protocol.classpath e executá-la na implementação do Oracle JVM para funcionar como um 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(); } } 

Caso você esteja usando outra implementação da JVM, defina a propriedade do 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 .Corda)

Uma extensão para a resposta de Dilums :

Sem alterar o código, você provavelmente precisará buscar implementações personalizadas de interfaces relacionadas a URL, como a Dilum recomenda. Para simplificar as coisas para você, recomendo consultar a fonte dos resources do Spring Framework . Embora o código não esteja na forma de um gerenciador de stream, ele foi projetado para fazer exatamente o que você está procurando fazer e está sob a licença ASL 2.0, tornando-o amigável o suficiente para reutilização em seu código com o devido crédito.

Eu não sei se já existe um, mas você pode fazê-lo facilmente.

Esse exemplo de protocolos diferentes me parece um padrão de fachada. Você tem uma interface comum quando existem diferentes implementações para cada caso.

Você pode usar o mesmo princípio, criar uma class ResourceLoader que retire a string do seu arquivo de propriedades e verifique se há um protocolo personalizado.

 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 

retira o myprotocol: desde o início da string e, em seguida, toma a decisão de qual caminho carregar o recurso e fornece apenas o recurso.

Eu tento evitar a class de URL e, em vez disso, confio no URI . Assim, para coisas que precisam de URL onde eu gostaria de fazer o Spring Resource, como lookup sem Spring, eu faço o seguinte:

 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 criar um URI, você pode usar o URI.create(..) . Desta forma também é melhor porque você controla o ClassLoader que fará a pesquisa de resources.

Eu notei algumas outras respostas tentando analisar o URL como uma String para detectar o esquema. Eu acho que é melhor passar URI e usá-lo para analisar em vez disso.

Eu realmente arquivei um problema há algum tempo atrás com o Spring Source, implorando-os para separarem o seu código Resource do core para que você não precise de todas as outras coisas do Spring.

Em um aplicativo Spring Boot, usei o seguinte para obter o URL do arquivo,

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