Escaneado classpath / modulepath en tiempo de ejecución en Java 9

Parece que no puedo encontrar información sobre si todavía es posible escanear todas las clases disponibles (para interfaces, anotaciones, etc.) en tiempo de ejecución, de la forma en que Spring, Reflections y muchos otros frameworks y bibliotecas lo hacen actualmente, frente a los cambios relacionados con Jigsaw en la forma en que se cargan las clases

EDITAR : Esta pregunta se trata de escanear las rutas reales de archivos físicos en busca de clases. La otra pregunta es sobre cargar dinámicamente clases y recursos. Está relacionado pero no es un duplicado .

ACTUALIZACIÓN : proyecto de Jetty ha hecho una propuesta de JEP para una API estandarizada para esto. Si tienes una manera de ayudar a hacer esta realidad, hazlo. De lo contrario, espera y espera.

ACTUALIZACIÓN 2 : Encontré esta publicación relevante. Citando el fragmento de código para la posteridad:

Si realmente está buscando obtener los contenidos de los módulos en la capa de inicio (los módulos que se resuelven al inicio), entonces hará algo como esto:

ModuleLayer.boot().configuration().modules().stream() .map(ResolvedModule::reference) .forEach(mref -> { System.out.println(mref.descriptor().name()); try (ModuleReader reader = mref.open()) { reader.list().forEach(System.out::println); } catch (IOException ioe) { throw new UncheckedIOException(ioe); } }); 

El siguiente código logra escanear la ruta del módulo en Java 9+ (Jigsaw). Encuentra todas las clases en la stack de llamadas, luego para cada referencia de clase, llama a classRef.getModule().getLayer().getConfiguration().modules() , que devuelve aa List , en lugar de simplemente List . ( ResolvedModule le da acceso a los recursos del módulo, mientras que Module no). Dado una referencia ResolvedModule para cada módulo, puede llamar al método .reference() para obtener ModuleReference para un módulo. ModuleReference#open() le da un ModuleReader , que le permite listar los recursos en un módulo, usando ModuleReader#list() , o abrir un recurso usando Optional ModuleReader#open(resourcePath) u Optional ModuleReader#read(resourcePath) . A continuación, cierra ModuleReader cuando haya terminado con el módulo. Esto no está documentado en ningún lado que haya visto. Fue muy difícil entender todo esto. Pero aquí está el código, con la esperanza de que alguien más se beneficie de esto.

Tenga en cuenta que incluso en JDK9 +, puede utilizar elementos de ruta de clase tradicionales junto con elementos de ruta de módulo, por lo que para una ruta de módulo completa + exploración de ruta de clase , probablemente debería utilizar una solución de exploración de ruta de clases adecuada, como FastClasspathScanner , que admite exploración de módulo utilizando el siguiente mecanismo, a partir de la versión 3.0.0 (descargo de responsabilidad, yo soy el autor).

 import java.lang.module.ModuleReader; import java.lang.module.ModuleReference; import java.lang.module.ResolvedModule; import java.net.URI; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.Deque; import java.util.HashSet; import java.util.List; import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.stream.Stream; public class ModuleScanner { private static CallerResolver CALLER_RESOLVER; static { try { // This can fail if the current SecurityManager does not allow // RuntimePermission ("createSecurityManager"): CALLER_RESOLVER = new CallerResolver(); } catch (final SecurityException e) { } } private static final class CallerResolver extends SecurityManager { /** Get classes in the call stack. */ @Override protected Class[] getClassContext() { return super.getClassContext(); } } /** Recursively find the topological sort order of ancestral layers. */ private static void findLayerOrder(ModuleLayer layer, Set layerVisited, Deque layersOut) { if (layerVisited.add(layer)) { List parents = layer.parents(); for (int i = 0; i < parents.size(); i++) { findLayerOrder(parents.get(i), layerVisited, layersOut); } layersOut.push(layer); } } /** * Return true if the given module name is a system module. There can be * system modules in layers above the boot layer. */ private static boolean isSystemModule(final String moduleName) { return moduleName.startsWith("java.") || moduleName.startsWith("javax.") || moduleName.startsWith("javafx.") || moduleName.startsWith("jdk.") || moduleName.startsWith("oracle."); } public static void main(String[] args) throws Exception { Deque layerOrder = new ArrayDeque<>(); Set layerVisited = new HashSet<>(); // Get ModuleReferences for modules of all classes in call stack if (CALLER_RESOLVER == null) { throw new RuntimeException("Current SecurityManager does not grant " + "RuntimePermission(\"createSecurityManager\")"); } else { final Class[] callStack = CALLER_RESOLVER.getClassContext(); if (callStack == null) { throw new RuntimeException(CallerResolver.class.getSimpleName() + "#getClassContext() returned null"); } else { for (int i = 0; i < callStack.length; i++) { ModuleLayer layer = callStack[i].getModule().getLayer(); if (layer != null) { findLayerOrder(layer, layerVisited, layerOrder); } else { System.out.println( "layer is null for unnamed modules -- have to " + "find their URLs by reading java.class.path"); } } } } // Lastly add system modules from boot layer ModuleLayer bootLayer = ModuleLayer.boot(); findLayerOrder(bootLayer, layerVisited, layerOrder); Set addedModules = new HashSet<>(); List> systemModuleRefs = new ArrayList<>(); List> nonSystemModuleRefs = new ArrayList<>(); for (ModuleLayer layer : layerOrder) { List modulesInLayer = new ArrayList<>( layer.configuration().modules()); Collections.sort(modulesInLayer, (e1, e2) -> e1.reference().descriptor().name() .compareTo(e2.reference().descriptor().name())); for (ResolvedModule module : modulesInLayer) { ModuleReference moduleReference = module.reference(); if (addedModules.add(moduleReference)) { String moduleName = moduleReference.descriptor().name(); (isSystemModule(moduleName) ? systemModuleRefs : nonSystemModuleRefs).add( new SimpleEntry<>(moduleReference, layer)); } } } // List system modules System.out.println("\nSYSTEM MODULES:\n"); for (Entry e : systemModuleRefs) { ModuleReference ref = e.getKey(); System.out.println(" " + ref.descriptor().name()); } // Show info for non-system modules System.out.println("\nNON-SYSTEM MODULES:"); for (Entry e : nonSystemModuleRefs) { ModuleReference ref = e.getKey(); ModuleLayer layer = e.getValue(); System.out.println("\n " + ref.descriptor().name()); System.out.println( " Version: " + ref.descriptor().toNameAndVersion()); System.out.println(" Packages: " + ref.descriptor().packages()); System.out.println(" ClassLoader: " + layer.findLoader(ref.descriptor().name())); Optional location = ref.location(); if (location.isPresent()) { System.out.println(" Location: " + location.get()); } try (ModuleReader moduleReader = ref.open()) { Stream stream = moduleReader.list(); stream.forEach(s -> System.out.println(" File: " + s)); } } } } 

El verdadero problema aquí es encontrar las rutas a todos los archivos jar y carpetas en el classpath. Una vez que los tenga, puede escanear.

Lo que hice fue lo siguiente:

  • obtener el descriptor del módulo actual para la clase actual
  • obtener todos los módulos requires
  • para cada uno de dichos módulos abre el recurso de MANIFEST.MF
  • elimine la ruta MANIFEST.MF de la URL del recurso
  • lo que queda es la ruta de clase del módulo, es decir, su jar o carpeta.

Hago lo mismo para el módulo actual, para obtener el classpath para el código actual.

De esta forma, recopilo classpath de un módulo actualmente en funcionamiento y todos sus módulos necesarios (1 paso de distancia). Eso funcionó para mí, y mi escáner Java8 aún podía hacer el trabajo. Este enfoque no requiere ninguna bandera VM adicional, etc.

Podría extender este enfoque para obtener todos los módulos requeridos fácilmente (no solo el primer nivel), pero por ahora, no los necesito.

Código .