Comment accéder en toute sécurité aux URL de tous les fichiers de ressources du classpath dans Java 9/10?

Nous avons appris des notes de publication de Java 9 que

Le chargeur de classe d’application n’est plus une instance de java.net.URLClassLoader (un détail d’implémentation qui n’a jamais été spécifié dans les versions précédentes). Le code qui suppose que ClassLoader :: getSytemClassLoader renvoie un object URLClassLoader devra être mis à jour.

Cela casse l’ancien code, qui parsing le classpath comme suit:

Java <= 8

URL[] ressources = ((URLClassLoader) classLoader).getURLs(); 

qui se heurte à un

 java.lang.ClassCastException: java.base/jdk.internal.loader.ClassLoaders$AppClassLoader cannot be cast to java.base/java.net.URLClassLoader 

Ainsi, pour Java 9+, la solution de contournement suivante a été proposée en tant que PR sur le projet Apache Ignite , qui fonctionne comme prévu dans les options d’exécution de JVM: --add-opens java.base/jdk.internal.loader=ALL-UNNAMED . Cependant, comme mentionné dans les commentaires ci-dessous, ce PR n’a jamais été intégré dans leur twig Master.

 /* * Java 9 + Bridge to obtain URLs from classpath... */ private static URL[] getURLs(ClassLoader classLoader) { URL[] urls = new URL[0]; try { //see https://github.com/apache/ignite/pull/2970 Class builtinClazzLoader = Class.forName("jdk.internal.loader.BuiltinClassLoader"); if (builtinClazzLoader != null) { Field ucpField = builtinClazzLoader.getDeclaredField("ucp"); ucpField.setAccessible(true); Object ucpObject = ucpField.get(classLoader); Class clazz = Class.forName("jdk.internal.loader.URLClassPath"); if (clazz != null && ucpObject != null) { Method getURLs = clazz.getMethod("getURLs"); if (getURLs != null) { urls = (URL[]) getURLs.invoke(ucpObject); } } } } catch (NoSuchMethodException | InvocationTargetException | NoSuchFieldException | IllegalAccessException | ClassNotFoundException e) { logger.error("Could not obtain classpath URLs in Java 9+ - Exception was:"); logger.error(e.getLocalizedMessage(), e); } return urls; } 

Cependant, cela provoque de graves maux de tête dus à l’utilisation de Reflection ici. C’est un peu un anti-pattern et est ssortingctement critiqué par le plugin interdit-apis maven :

Invocation d’une méthode interdite: java.lang.reflect.AccessibleObject # setAccessible (boolean) [L’utilisation de Reflection pour contourner les indicateurs d’access échoue avec SecurityManagers et ne fonctionnera probablement plus sur les classes d’exécution de Java 9]

Question

Existe-t-il un moyen sûr d’accéder à la liste de toutes les URLs ressources du chemin de classe / module, accessible par le classloader donné, dans OpenJDK 9/10 sans utiliser les importations sun.misc.* (Par exemple en utilisant Unsafe )?

MISE À JOUR (liée aux commentaires)

Je sais que je peux faire

  Ssortingng[] pathElements = System.getProperty("java.class.path").split(System.getProperty("path.separator")); 

pour obtenir les éléments dans le classpath et ensuite les parsingr en URL . Cependant, pour autant que je sache, cette propriété ne renvoie que le chemin de classe indiqué au moment du lancement de l’application. Toutefois, dans un environnement de conteneur, ce sera celui du serveur d’applications et pourrait ne pas suffire, par exemple en utilisant ensuite des bundles EAR.

MISE À JOUR 2

Merci pour tous vos commentaires. Je vais tester si System.getProperty("java.class.path") fonctionnera à nos fins et mettra à jour la question, si cela répond à nos besoins.

Cependant, il semble que d’ autres projets (peut-être pour d’autres raisons, par exemple Apache TomEE 8) souffrent de la même douleur liée à URLClassLoader – pour cette raison, je pense que la question est toujours valable.

Je pense que c’est un problème XY . L’access aux URL de toutes les ressources du classpath n’est pas une opération prise en charge en Java et ce n’est pas une bonne chose à faire. Comme vous l’avez déjà vu dans cette question, vous allez vous battre contre le cadre si vous essayez de le faire. Il y aura un million de cas extrêmes qui casseront votre solution (chargeurs de classes personnalisés, conteneurs EE, etc., etc.).

S’il vous plaît pourriez-vous expliquer pourquoi vous voulez faire cela?

Si vous avez une sorte de système de plug-in et que vous recherchez des modules qui s’interfacent avec votre code et qui peuvent avoir été fournis à l’exécution, vous devez utiliser l’API ServiceLoader , à savoir:

Un fournisseur de services intégré à un fichier JAR pour le chemin de classe est identifié en plaçant un fichier de configuration de fournisseur dans le répertoire de ressources META-INF/services . Le nom du fichier de configuration fournisseur est le nom binary complet du service. Le fichier de configuration du fournisseur contient une liste de noms binarys complets de fournisseurs de services, un par ligne. Par exemple, supposons que le fournisseur de service com.example.impl.StandardCodecs soit empaqueté dans un fichier JAR pour le chemin de classe. Le fichier JAR contiendra un fichier de configuration de fournisseur nommé:

 META-INF/services/com.example.CodecFactory 

qui contient la ligne:

 com.example.impl.StandardCodecs # Standard codecs 

AFAIK, vous pouvez parsingr la propriété système java.class.path pour obtenir les URL:

 Ssortingng classpath = System.getProperty("java.class.path"); Ssortingng[] ensortinges = classpath.split(File.pathSeparator); URL[] result = new URL[ensortinges.length]; for(int i = 0; i < entries.length; i++) { result[i] = Paths.get(entries[i]).toAbsolutePath().toUri().toURL(); } System.out.println(Arrays.toString(result)); // eg [file:/J:/WS/Oxygen-Stable/jdk10/bin/]