URL de chargement des ressources du classpath en Java

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 bien le chargement réel de la ressource de l’application qui a besoin de la ressource, et comme une URL n’est qu’une chaîne, le chargement des ressources est également très facilement configurable.

Existe-t-il un protocole pour charger les ressources en utilisant le classloader actuel? Ceci est similaire au protocole Jar, sauf que je n’ai pas besoin de savoir quel fichier JAR ou dossier de classe provient de la ressource.

Je peux le faire en utilisant Class.getResourceAsStream("a.xml") , bien sûr, mais cela nécessiterait que j’utilise une API différente, et donc des modifications au code existant. Je veux pouvoir l’utiliser dans tous les endroits où je peux spécifier une URL pour la ressource, simplement en mettant à jour un fichier de propriétés.

Introduction et implémentation de base

Tout d’abord, vous allez avoir besoin d’au moins un URLStreamHandler. Cela ouvrira la connexion à une URL donnée. Notez que cela s’appelle simplement Handler ; cela vous permet de spécifier java -Djava.protocol.handler.pkgs=org.my.protocols et il sera automatiquement pris en compte, en utilisant le nom de package “simple” comme protocole supporté (dans ce cas “classpath”).

Usage

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

Problèmes de lancement

Si vous êtes comme moi, vous ne voulez pas dépendre d’une propriété en cours de lancement pour vous aider à trouver quelque chose (dans mon cas, j’aime garder mes options ouvertes comme Java WebStart – c’est pourquoi j’ai besoin de tout cela) ).

Solutions de contournement / améliorations

Code manuel Spécification du gestionnaire

Si vous contrôlez le code, vous pouvez le 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, ceci est moins que satisfaisant, car vous n’avez pas besoin d’une URL pour le faire – vous voulez le faire car certaines lib vous ne pouvez pas (ou ne voulez pas) contrôler les URL …

Enregistrement de JVM Handler

L’option ultime consiste à enregistrer un URLStreamHandlerFactory qui gérera toutes les URL à travers le 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(Ssortingng protocol, URLStreamHandler urlHandler) { protocolHandlers = new HashMap(); addHandler(protocol, urlHandler); } public void addHandler(Ssortingng protocol, URLStreamHandler urlHandler) { protocolHandlers.put(protocol, urlHandler); } public URLStreamHandler createURLStreamHandler(Ssortingng protocol) { return protocolHandlers.get(protocol); } } 

Pour enregistrer le gestionnaire, appelez URL.setURLStreamHandlerFactory() avec votre fabrique configurée. Ensuite, faites la new URL("classpath:org/my/package/resource.extension") comme le premier exemple et c’est parti.

Problème d’enregistrement du gestionnaire JVM

Notez que cette méthode ne peut être appelée qu’une seule fois par JVM et notez bien que Tomcat utilisera cette méthode pour enregistrer un gestionnaire JNDI (AFAIK). Essayez la jetée (je le serai); dans le pire des cas, vous pouvez utiliser la méthode en premier et ensuite vous devez travailler autour de vous!

Licence

Je le publie dans le domaine public et demande que, si vous souhaitez le modifier, vous lancez un projet OSS quelque part et commentez ici avec les détails. Une meilleure implémentation serait d’avoir un URLStreamHandlerFactory qui utilise ThreadLocal s pour stocker URLStreamHandler s pour chaque Thread.currentThread().getContextClassLoader() . Je vais même vous donner mes modifications et mes cours 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 l’avez déjà avec

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

Comme expliqué dans la documentation du spring et souligné dans les commentaires de skaffman.

Vous pouvez également définir la propriété par programme lors du démarrage:

 final Ssortingng key = "java.protocol.handler.pkgs"; Ssortingng newValue = "org.my.protocols"; if (System.getProperty(key) != null) { final Ssortingng 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 du 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 lors de l’appel préalable d’une méthode ou de son absence dans le bon conteneur. Il y a aussi une classe d’exception si vous avez des problèmes:

 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.comstack( "(.+\\.protocols)\\.[^\\.]+" ); /** * Call this method with your handlerclass * @param handlerClass * @throws Exception */ public static void add( Class handlerClass ) throws Exception { if ( handlerClass.getSimpleName().equals( "Handler" ) ) { Ssortingng pkgName = handlerClass.getPackage().getName(); Matcher m = packagePattern.matcher( pkgName ); if ( m.matches() ) { Ssortingng 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( Ssortingng handlerPackage ) { // this property controls where java looks for // stream handlers - always uses current value. final Ssortingng key = "java.protocol.handler.pkgs"; Ssortingng newValue = handlerPackage; if ( System.getProperty( key ) != null ) { final Ssortingng 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(Ssortingng 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 classpath: protocol).

Cependant, Java vous permet d’append vos propres protocoles. Cela se fait en fournissant des implémentations concrètes java.net.URLStreamHandler et java.net.URLConnection .

Cet article décrit comment un gestionnaire de stream personnalisé peut être implémenté: http://java.sun.com/developer/onlineTraining/protocolhandlers/ .

Bien entendu, la solution pour enregistrer URLStreamHandlers est la plus correcte, mais la solution la plus simple est parfois nécessaire. Donc, j’utilise la méthode suivante pour cela:

 /** * 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(Ssortingng path) throws IOException { InputStream is; Ssortingng 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(); Ssortingng 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; }

Inspirer par @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() 

Il suffit de créer cette classe dans le package sun.net.www.protocol.classpath et de l’exécuter dans l’implémentation Oracle JVM 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 .

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

Une extension de la réponse de Dilums :

Sans changer de code, vous devrez probablement poursuivre les implémentations personnalisées des interfaces liées aux URL, comme Dilum vous le recommande. Pour simplifier les choses, je peux vous recommander d’examiner la source des ressources de Spring Framework . Bien que le code ne soit pas sous la forme d’un gestionnaire de stream, il a été conçu pour faire exactement ce que vous souhaitez faire et se trouve sous la licence ASL 2.0, ce qui le rend suffisamment convivial pour être réutilisé dans votre code.

Je ne sais pas s’il y en a déjà un, mais vous pouvez le faire vous-même facilement.

Cet exemple de protocole différent me semble être un motif de façade. Vous disposez d’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 prend la chaîne de votre fichier de propriétés et vérifier la présence d’un protocole 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 protocole: du début de la chaîne, puis décide de la manière de charger la ressource et vous donne la ressource.

J’essaie d’éviter la classe URL et je m’appuie plutôt sur l’ URI . Ainsi, pour les choses qui nécessitent une URL où je voudrais faire des ressources Spring comme la recherche sans Spring, je fais ce qui suit:

 public static URL toURL(URI u, ClassLoader loader) throws MalformedURLException { if ("classpath".equals(u.getScheme())) { Ssortingng path = u.getPath(); if (path.startsWith("/")){ path = path.subssortingng("/".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 une URI, vous pouvez utiliser URI.create(..) . Cette méthode est également préférable car vous contrôlez le ClassLoader qui effectuera la recherche des ressources.

J’ai remarqué d’autres réponses en essayant d’parsingr l’URL en tant que chaîne pour détecter le schéma. Je pense qu’il vaut mieux faire circuler l’URI et l’utiliser pour parsingr à la place.

J’ai en fait déposé un problème il y a quelque temps avec Spring Source en leur demandant de séparer leur code de ressources du core afin de ne pas avoir besoin de toutes les autres fonctionnalités Spring.

Dans une application Spring Boot, j’ai utilisé ce qui suit pour obtenir l’URL du fichier,

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