En Java, vous pouvez charger toutes sortes de ressources en utilisant la même API mais avec des protocoles d'URL différents:
file:///tmp.txt
http://127.0.0.1:8080/a.properties
jar:http://www.foo.com/bar/baz.jar!/COM/foo/Quux.class
Cela dissocie joliment le chargement réel de la ressource de l’application qui en a besoin, puisqu’une URL n’est qu’une chaîne, le chargement de la ressource est également très facilement configurable.
Existe-t-il un protocole permettant de charger des ressources à l'aide du chargeur de classes actuel? Cette procédure est similaire au protocole Jar, sauf que je n'ai pas besoin de savoir de quel fichier jar ou dossier de classe provient la ressource.
Je peux le faire en utilisant Class.getResourceAsStream("a.xml")
, bien sûr, mais cela me demanderait d’utiliser une API différente et donc de modifier le code existant. Je veux pouvoir l'utiliser dans tous les endroits où je peux déjà spécifier une URL pour la ressource, simplement en mettant à jour un fichier de propriétés.
Tout d'abord, vous aurez besoin d'au moins un URLStreamHandler. Cela ouvrira réellement la connexion à une URL donnée. Notez que ceci s'appelle simplement Handler
; cela vous permet de spécifier Java -Djava.protocol.handler.pkgs=org.my.protocols
et il sera automatiquement récupéré, en utilisant le nom de package "simple" comme protocole pris en charge (dans ce cas, "classpath").
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();
}
}
Si vous contrôlez le code, vous pouvez faire
new URL(null, "classpath:some/package/resource.extension", new org.my.protocols.classpath.Handler(ClassLoader.getSystemClassLoader()))
et cela utilisera votre gestionnaire pour ouvrir la connexion.
Mais encore une fois, c'est moins que satisfaisant, car vous n'avez pas besoin d'une URL pour le faire - vous voulez le faire parce que certaines bibliothèques vous ne pouvez pas (ou ne voulez pas) contrôler les URL souhaitées ...
L'option ultime consiste à enregistrer une URLStreamHandlerFactory
qui gérera toutes les URL du 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);
}
}
Pour enregistrer le gestionnaire, appelez URL.setURLStreamHandlerFactory()
avec votre fabrique configurée. Ensuite, faites new URL("classpath:org/my/package/resource.extension")
comme le premier exemple et c'est parti.
Notez que cette méthode ne peut être appelée qu'une fois par machine virtuelle et notez bien que Tomcat l'utilisera pour enregistrer un gestionnaire JNDI (autant que je sache). Essayez Jetty (je le serai); dans le pire des cas, vous pouvez utiliser la méthode d'abord et ensuite, elle doit fonctionner autour de vous!
Je libère ceci dans le domaine public, et demande si vous souhaitez modifier que vous démarriez un projet OSS quelque part et commentez ici avec les détails. Une meilleure implémentation serait d'avoir une URLStreamHandlerFactory
qui utilise ThreadLocal
s pour stocker URLStreamHandler
s pour chaque Thread.currentThread().getContextClassLoader()
. Je vais même vous donner mes modifications et les classes de test.
URL url = getClass().getClassLoader().getResource("someresource.xxx");
Ça devrait le faire.
Je pense que cela vaut sa propre réponse - si vous utilisez Spring, vous avez déjà cela avec
Resource firstResource =
context.getResource("http://www.google.fi/");
Resource anotherResource =
context.getResource("classpath:some/resource/path/myTemplate.txt");
Comme expliqué dans la documentation de printemps et souligné dans les commentaires de skaffman.
Vous pouvez également définir la propriété par programmation lors du démarrage:
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);
En utilisant cette classe:
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();
}
}
Ainsi, vous obtenez le moyen le moins intrusif de le faire. :) Java.net.URL utilisera toujours la valeur actuelle des propriétés système.
J'ai créé une classe qui aide à réduire les erreurs lors de la configuration des gestionnaires personnalisés et tire parti de la propriété système pour éviter tout problème d'appeler une méthode en premier ou de ne pas figurer dans le bon conteneur. Il y a aussi une classe d'exception si vous vous trompez:
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 );
}
}
(Similaire à la réponse d'Azder , mais un tact légèrement différent.)
Je ne crois pas qu'il existe un gestionnaire de protocole prédéfini pour le contenu du classpath. (Le soi-disant protocole classpath:
).
Cependant, Java vous permet d'ajouter vos propres protocoles. Ceci est fait en fournissant des implémentations concrètes Java.net.URLStreamHandler
et Java.net.URLConnection
.
Cet article explique comment un gestionnaire de flux personnalisé peut être implémenté: http://Java.Sun.com/developer/onlineTraining/protocolhandlers/.
Inspire by @Stephen https://stackoverflow.com/a/1769454/980442 Et http://docstore.mik.ua/orelly/Java/exp/ch09_06.htm
Utiliser
new URL("classpath:org/my/package/resource.extension").openConnection()
créez simplement cette classe dans le package Sun.net.www.protocol.classpath
et exécutez-la dans l'implémentation JVM d'Oracle pour fonctionner comme un charme.
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();
}
}
Si vous utilisez une autre implémentation JVM, définissez la propriété système Java.protocol.handler.pkgs=Sun.net.www.protocol
.
Une extension de La réponse de Dilums :
Sans changer de code, il est probablement nécessaire de poursuivre l'implémentation personnalisée d'interfaces liées aux URL, comme le recommande Dilum. Pour vous simplifier la vie, je vous recommande de consulter la source de Ressources de Spring Framework . Bien que le code ne se présente pas sous la forme d'un gestionnaire de flux, il a été conçu pour faire exactement ce que vous cherchez et est sous licence ASL 2.0, le rendant ainsi assez convivial pour le réutiliser correctement dans votre code.
La solution avec l'enregistrement de URLStreamHandlers est bien sûr la solution la plus correcte, mais parfois, la solution la plus simple est nécessaire. Donc, j'utilise la méthode suivante pour cela:
/**
* 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;
}
À partir de Java 9+, vous pouvez définir une nouvelle URLStreamHandlerProvider
. La classe URL
utilise la structure du chargeur de service pour le charger au moment de l'exécution.
Créer un fournisseur:
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;
}
}
Créez un fichier appelé Java.net.spi.URLStreamHandlerProvider
dans le répertoire META-INF/services
avec le contenu:
org.example.ClasspathURLStreamHandlerProvider
Maintenant, la classe d'URL utilisera le fournisseur quand elle verra quelque chose comme:
URL url = new URL("classpath:myfile.txt");
Je ne sais pas s’il en existe déjà un, mais vous pouvez le faire vous-même facilement.
Cet exemple de protocole différent me ressemble à un motif de façade. Vous avez une interface commune lorsqu'il existe différentes implémentations pour chaque cas.
Vous pouvez utiliser le même principe, créer une classe ResourceLoader qui extrait la chaîne de votre fichier de propriétés et vérifie si notre protocole est personnalisé.
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
supprime le myprotocol: à partir du début de la chaîne, puis décide du mode de chargement de la ressource et vous donne simplement la ressource.
Si vous avez Tomcat sur le chemin de classe, c'est aussi simple que:
TomcatURLStreamHandlerFactory.register();
Ceci enregistrera les gestionnaires pour les protocoles "war" et "classpath".
J'essaie d'éviter la classe URL
et je m'appuie plutôt sur URI
. Ainsi, pour les choses qui ont besoin de URL
où je voudrais faire Spring Resource comme lookup sans Spring, je fais ce qui suit:
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();
}
}
Pour créer un URI, vous pouvez utiliser URI.create(..)
. Cette méthode est également préférable, car vous contrôlez la ClassLoader
qui effectuera la recherche des ressources.
J'ai remarqué d'autres réponses essayant d'analyser l'URL sous forme de chaîne pour détecter le schéma. Je pense que c'est mieux de passer l'URI et de l'utiliser à la place pour analyser.
Dans une application Spring Boot, j'ai utilisé ce qui suit pour obtenir l'URL du fichier,
Thread.currentThread().getContextClassLoader().getResource("PromotionalOfferIdServiceV2.wsdl")