Сканирование classpath/modulepath во время выполнения в Java 9

Кажется, я не могу найти информацию о том, возможно ли сканирование всех доступных классов (для интерфейсов, аннотаций и т. Д.) Во время выполнения, как это делают Spring, Reflections и многие другие фреймворки и библиотеки в настоящее время, несмотря на изменения Jigsaw, связанные с способ загрузки классов.

РЕДАКТИРОВАТЬ: Этот вопрос о сканировании реальных физических путей к файлам в поисках классов. Другой вопрос касается динамической загрузки классов и ресурсов. Это связано, но очень не дубликат.

ОБНОВЛЕНИЕ: проект Jetty сделал предложение JEP для стандартизированного API для этого. Если у вас есть способ помочь сделать это реальностью, сделайте это. В противном случае, ждать и надеяться.

ОБНОВЛЕНИЕ 2: нашел этот соответствующий звучащий пост. Цитирую фрагмент кода для потомков:

Если вы действительно хотите узнать содержимое модулей в загрузочном слое (модули, которые разрешаются при запуске), то вы сделаете что-то вроде этого:

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

2 ответа

Решение

Следующий код выполняет сканирование пути модуля в Java 9+ (Jigsaw). Он находит все классы в стеке вызовов, а затем для каждой ссылки на классы вызывает classRef.getModule().getLayer().getConfiguration().modules(), который возвращает аа List<ResolvedModule>, а не просто List<Module>, (ResolvedModule дает вам доступ к ресурсам модуля, тогда как Module не.) Учитывая ResolvedModule Ссылка для каждого модуля, вы можете позвонить .reference() способ получить ModuleReference для модуля. ModuleReference#open() дает вам ModuleReader, что позволяет перечислять ресурсы в модуле, используя ModuleReader#list()или открыть ресурс, используя Optional<InputStream> ModuleReader#open(resourcePath) или же Optional<ByteBuffer> ModuleReader#read(resourcePath), Затем вы закрываете ModuleReader когда вы закончите с модулем. Это не задокументировано нигде, что я видел. Было очень сложно все это понять. Но вот код, в надежде, что кто-то еще выиграет от этого.

Обратите внимание, что даже в JDK9+ вы все еще можете использовать традиционные элементы classpath вместе с элементами пути к модулю, поэтому для полного сканирования пути к модулю + путь к классу вам, вероятно, следует использовать правильное решение для сканирования пути к классам, такое как ClassGraph, которое поддерживает сканирование модулей с использованием приведенного ниже механизм (отказ от ответственности, я автор).

package main;

import java.lang.StackWalker.Option;
import java.lang.module.ModuleReader;
import java.lang.module.ModuleReference;
import java.lang.module.ResolvedModule;
import java.net.URI;
import java.security.AccessController;
import java.security.PrivilegedAction;
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 Java9Scanner {

    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<ModuleLayer> visited, Deque<ModuleLayer> layersOut) {
        if (visited.add(layer)) {
            List<ModuleLayer> parents = layer.parents();
            for (int i = 0; i < parents.size(); i++) {
                findLayerOrder(parents.get(i), visited, layersOut);
            }
            layersOut.push(layer);
        }
    }

    /** Get ModuleReferences from a Class reference. */
    private static List<Entry<ModuleReference, ModuleLayer>> findModuleRefs(
            Class<?>[] callStack) {
        Deque<ModuleLayer> layerOrder = new ArrayDeque<>();
        Set<ModuleLayer> visited = new HashSet<>();
        for (int i = 0; i < callStack.length; i++) {
            ModuleLayer layer = callStack[i].getModule().getLayer();
            findLayerOrder(layer, visited, layerOrder);
        }
        Set<ModuleReference> addedModules = new HashSet<>();
        List<Entry<ModuleReference, ModuleLayer>> moduleRefs = new ArrayList<>();
        for (ModuleLayer layer : layerOrder) {
            Set<ResolvedModule> modulesInLayerSet = layer.configuration()
                    .modules();
            final List<Entry<ModuleReference, ModuleLayer>> modulesInLayer =
                    new ArrayList<>();
            for (ResolvedModule module : modulesInLayerSet) {
                modulesInLayer
                        .add(new SimpleEntry<>(module.reference(), layer));
            }
            // Sort modules in layer by name for consistency
            Collections.sort(modulesInLayer,
                    (e1, e2) -> e1.getKey().descriptor().name()
                            .compareTo(e2.getKey().descriptor().name()));
            // To be safe, dedup ModuleReferences, in case a module occurs in multiple
            // layers and reuses its ModuleReference (no idea if this can happen)
            for (Entry<ModuleReference, ModuleLayer> m : modulesInLayer) {
                if (addedModules.add(m.getKey())) {
                    moduleRefs.add(m);
                }
            }
        }
        return moduleRefs;
    }

    /** Get the classes in the call stack. */
    private static Class<?>[] getCallStack() {
        // Try StackWalker (JDK 9+)
        PrivilegedAction<Class<?>[]> stackWalkerAction = new PrivilegedAction<Class<?>[]>() {
            @Override
            public Class<?>[] run() {
                List<Class<?>> stackFrameClasses = new ArrayList<>();
                StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE)
                        .forEach(sf -> stackFrameClasses
                                .add(sf.getDeclaringClass()));
                return stackFrameClasses.toArray(new Class<?>[0]);
            }
        };
        try {
            // Try with doPrivileged()
            return AccessController
                    .doPrivileged(stackWalkerAction);
        } catch (Exception e) {
        }
        try {
            // Try without doPrivileged()
            return stackWalkerAction.run();
        } catch (Exception e) {
        }

        // Try SecurityManager
        PrivilegedAction<Class<?>[]> callerResolverAction = new PrivilegedAction<Class<?>[]>() {
            @Override
            public Class<?>[] run() {
                return new CallerResolver().getClassContext();
            }
        };
        try {
            // Try with doPrivileged()
            return AccessController
                    .doPrivileged(callerResolverAction);
        } catch (Exception e) {
        }
        try {
            // Try without doPrivileged()
            return callerResolverAction.run();
        } catch (Exception e) {
        }

        // As a fallback, use getStackTrace() to try to get the call stack
        try {
            throw new Exception();
        } catch (final Exception e) {
            final List<Class<?>> classes = new ArrayList<>();
            for (final StackTraceElement elt : e.getStackTrace()) {
                try {
                    classes.add(Class.forName(elt.getClassName()));
                } catch (final Throwable e2) {
                    // Ignore
                }
            }
            if (classes.size() > 0) {
                return classes.toArray(new Class<?>[0]);
            } else {
                // Last-ditch effort -- include just this class in the call stack
                return new Class<?>[] { Java9Scanner.class };
            }
        }
    }

    /**
     * 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 ModuleReference moduleReference) {
        URI location = moduleReference.location().orElse(null);
        if (location == null) {
            return true;
        }
        final String scheme = location.getScheme();
        return scheme != null && scheme.equalsIgnoreCase("jrt");
    }

    public static void main(String[] args) throws Exception {
        // Get ModuleReferences for modules of all classes in call stack,
        List<Entry<ModuleReference, ModuleLayer>> systemModuleRefs = new ArrayList<>();
        List<Entry<ModuleReference, ModuleLayer>> nonSystemModuleRefs = new ArrayList<>();

        Class<?>[] callStack = getCallStack();
        List<Entry<ModuleReference, ModuleLayer>> moduleRefs = findModuleRefs(
                callStack);
        // Split module refs into system and non-system modules based on module name
        for (Entry<ModuleReference, ModuleLayer> m : moduleRefs) {
            (isSystemModule(m.getKey()) ? systemModuleRefs
                    : nonSystemModuleRefs).add(m);
        }

        // List system modules
        System.out.println("\nSYSTEM MODULES:\n");
        for (Entry<ModuleReference, ModuleLayer> 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<ModuleReference, ModuleLayer> 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<URI> location = ref.location();
            if (location.isPresent()) {
                System.out.println("    Location: " + location.get());
            }
            try (ModuleReader moduleReader = ref.open()) {
                Stream<String> stream = moduleReader.list();
                stream.forEach(s -> System.out.println("      File: " + s));
            }
        }
    }
}

Здесь проблема заключается в том, чтобы найти пути ко всем банкам и папкам на пути к классам. Один раз, когда они у вас есть, вы можете сканировать.

Я сделал следующее:

  • получить текущий дескриптор модуля для текущего класса
  • получить все requires модули
  • для каждого такого модуля открыт ресурс MANIFEST.MF
  • удалить MANIFEST.MF путь от URL ресурса
  • остается только путь к классу модуля, то есть к его банке или папке.

Я делаю то же самое для текущего модуля, чтобы получить classpath для текущего кода.

Таким образом, я собираю classpath работающего в данный момент модуля и всех необходимых модулей (1 шаг). Это работало на меня - и мой сканер Java8 все еще был в состоянии сделать работу. Этот подход не требует каких-либо дополнительных флагов VM и т. Д.

Я мог бы расширить этот подход, чтобы легко получить все необходимые модули (не только первый уровень), но пока мне это не нужно.

Код

Другие вопросы по тегам