web-dev-qa-db-fra.com

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

Nous avons appris des notes de version 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 objet URLClassLoader devra être mis à jour.

Cela rompt l'ancien code, qui analyse le chemin de classe comme suit:

Java <= 8

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

qui se déroule dans un

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

Donc, pour Java 9 + la solution de contournement suivante a été proposée comme PR au Apache Ignite Project , qui fonctionne comme prévu compte tenu des ajustements dans les options d'exécution JVM: --add-opens Java.base/jdk.internal.loader=ALL-UNNAMED. Cependant, comme mentionné dans les commentaires ci-dessous, ce PR n'a jamais été fusionné dans sa branche principale.

/*
 * 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 en raison de l'utilisation de Reflection ici. Ceci est une sorte d'anti-modèle et est strictement critiqué par le plugin maven interdit-apis :

Invocation de méthode interdite: Java.lang.reflect.AccessibleObject # setAccessible (boolean) [L'utilisation de la réflexion pour contourner les indicateurs d'accès échoue avec SecurityManagers et ne fonctionnera probablement plus sur les classes d'exécution dans Java 9]

Question

Existe-t-il un moyen sûr d'accéder à la liste de toutes les ressources URLs dans le chemin de classe/module, accessible par le donné un chargeur de classe, 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

 String[] pathElements = System.getProperty("Java.class.path").split(System.getProperty("path.separator"));

pour obtenir les éléments du chemin de classe, puis les analyser dans URLs. Cependant - pour autant que je sache - cette propriété ne renvoie que le chemin de classe donné au moment du lancement de l'application. Cependant, dans un environnement de conteneur, ce sera celui du serveur d'applications et pourrait ne pas être suffisant, par ex. puis en utilisant les bundles EAR.

MISE À JOUR 2

Merci pour tous vos commentaires. Je vais tester si System.getProperty("Java.class.path") fonctionnera pour nos fins et mettre à 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 au URLClassLoader- pour cette raison, je pense que c'est une question valable.

44
rzo

Je pense que c'est n problème XY . L'accès aux URL de toutes les ressources sur le chemin de classe n'est pas une opération prise en charge dans Java et n'est pas une bonne chose à essayer. Comme vous l'avez déjà vu dans cette question, vous vous battrez contre si vous essayez de le faire, il y aura un million de cas Edge qui briseront votre solution (chargeurs de classe personnalisés, conteneurs EE, etc., etc.).

Pouvez-vous expliquer pourquoi vous souhaitez procéder ainsi?

Si vous avez une sorte de système de plugins et que vous recherchez des modules qui s'interfacent avec votre code qui peuvent avoir été fournis au moment de l'exécution, vous devez utiliser l'API ServiceLoader , c'est-à-dire:

Un fournisseur de services qui est empaqueté en tant que fichier JAR pour le chemin d'accès aux classes 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 du fournisseur est le nom binaire complet du service. Le fichier de configuration de fournisseur contient une liste de noms binaires pleinement qualifiés de fournisseurs de services, un par ligne. Par exemple, supposons que le fournisseur de services com.example.impl.StandardCodecs est 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
18
Rich

AFAIK vous pouvez analyser le Java.class.path propriété système pour obtenir les URL:

String classpath = System.getProperty("Java.class.path");
String[] entries = classpath.split(File.pathSeparator);
URL[] result = new URL[entries.length];
for(int i = 0; i < entries.length; i++) {
    result[i] = Paths.get(entries[i]).toAbsolutePath().toUri().toURL();
}

System.out.println(Arrays.toString(result)); // e.g. [file:/J:/WS/Oxygen-Stable/jdk10/bin/]
9
Jorn Vernee