Как получить classpath из загрузчика классов?

Я использую какой-то сторонний код, который при задании аргумента командной строки '-classpath' не устанавливает java.class.path, а вместо этого просто создает загрузчик классов, добавляет все URL-адреса для элементов в командной строке, указанных classpath, в загрузчик классов, а затем устанавливает его в качестве загрузчика классов контекста. В классе плагина для этого кода, который я написал, я получаю экземпляр этого загрузчика классов и каким-то образом должен использовать его, чтобы вернуть базовый путь к классу, чтобы я мог использовать его при вызове JavaCompiler.getTask(...) и скомпилировать другой код на лету. Однако, похоже, нет никакого способа получить ClassPath из ClassLoader, и, поскольку java.class.path не установлен, я не могу получить доступ к базовому пути к классам, с которым приложение изначально вызывалось... Есть идеи?

5 ответов

Решение

Если загрузчик классов использует URL-адреса, он должен быть URLClassloader, У вас есть доступ к URL-адресам, которые определяют путь к классам для него вместе с его родителем ClassLoader,

Чтобы получить URL, просто сделайте следующее:

((URLClassLoader) (Thread.currentThread().getContextClassLoader())).getURLs()

ОБНОВЛЕНИЕ: мой оригинальный ответ ниже, к сожалению, неадекватен, теперь, когда я потратил три года на разработку FastClasspathScanner и получил большое количество отчетов об ошибках в определенных средах пути к классам, не работающих с этой библиотекой. FastClasspathScanner теперь обрабатывает множество сложных механизмов спецификации пути к классам. Даже простой поиск пути к классам может быть безумно сложным в общем случае (не говоря уже о его сканировании), поскольку существует очень много способов добавить файлы jar и каталоги в путь к классам.

Во-первых, код, который я дал ниже, обрабатывает только URLClassLoader и многие основные среды выполнения и контейнеры не расширяют это, они реализуют свой собственный загрузчик классов с нуля. Но это становится намного сложнее, чем в случае с Java 9+, поскольку, хотя традиционный путь к классам все еще существует, в будущем все будет двигаться в направлении использования пути к модулю, а не пути к классам. Модули имеют URL-адреса, но они "jrt:/" URL, а не "file:/" URL-адреса и URL-адреса модулей на самом деле не содержат путь к файлу, а только имя модуля, поэтому вы даже не можете найти модуль на диске. Ваша единственная возможность - использовать (сильно инкапсулированную) систему модулей для работы с модулями. Я написал о том, как сканировать путь модуля здесь.

FastClasspathScanner обрабатывает множество сложных механизмов спецификации пути к классам, поэтому вам не нужно заново изобретать колесо. Вы можете получить список записей пути к классам из FastClasspathScanner - это избавит вас от попыток заставить что-то работать со всеми разнообразными механизмами спецификации путей к классам, которые вы обнаружите в дикой природе. (Приносим извинения за разрыв последней ссылки - API и документы для FCS скоро изменятся.)

-

[Старый ответ - устарел:]

Другие ответы верны в большинстве ситуаций, но это усложняется в некоторых ситуациях: например, Maven, Tomcat и JUnit имеют собственную поддержку пути к классам и не используют системный путь к классам.

На данный момент это самая полная система, которую мне удалось создать (этот код взят из моего проекта Fast Classpath Scanner):

/** The unique elements of the classpath, as an ordered list. */
private final ArrayList<File> classpathElements = new ArrayList<>();

/** The unique elements of the classpath, as a set. */
private final HashSet<String> classpathElementsSet = new HashSet<>();

/** Clear the classpath. */
private void clearClasspath() {
    classpathElements.clear();
    classpathElementsSet.clear();
}

/** Add a classpath element. */
private void addClasspathElement(String pathElement) {
    if (classpathElementsSet.add(pathElement)) {
        final File file = new File(pathElement);
        if (file.exists()) {
            classpathElements.add(file);
        }
    }
}

/** Parse the system classpath. */
private void parseSystemClasspath() {
    // Look for all unique classloaders.
    // Keep them in an order that (hopefully) reflects the order in which class resolution occurs.
    ArrayList<ClassLoader> classLoaders = new ArrayList<>();
    HashSet<ClassLoader> classLoadersSet = new HashSet<>();
    classLoadersSet.add(ClassLoader.getSystemClassLoader());
    classLoaders.add(ClassLoader.getSystemClassLoader());
    if (classLoadersSet.add(Thread.currentThread().getContextClassLoader())) {
        classLoaders.add(Thread.currentThread().getContextClassLoader());
    }
    // Dirty method for looking for any other classloaders on the call stack
    try {
        // Generate stacktrace
        throw new Exception();
    } catch (Exception e) {
        StackTraceElement[] stacktrace = e.getStackTrace();
        for (StackTraceElement elt : stacktrace) {
            try {
                ClassLoader cl = Class.forName(elt.getClassName()).getClassLoader();
                if (classLoadersSet.add(cl)) {
                    classLoaders.add(cl);
                }
            } catch (ClassNotFoundException e1) {
            }
        }
    }

    // Get file paths for URLs of each classloader.
    clearClasspath();
    for (ClassLoader cl : classLoaders) {
        if (cl != null) {
            for (URL url : ((URLClassLoader) cl).getURLs()) {
                if ("file".equals(url.getProtocol())) {
                    addClasspathElement(url.getFile());
                }
            }
        }
    }
}

/** Override the system classpath with a custom classpath to search. */
public FastClasspathScanner overrideClasspath(String classpath) {
    clearClasspath();
    for (String pathElement : classpath.split(File.pathSeparator)) {
        addClasspathElement(pathElement);
    }
    return this;
}

/**
 * Get a list of unique elements on the classpath (directories and files) as File objects, preserving order.
 * Classpath elements that do not exist are not included in the list.
 */
public ArrayList<File> getUniqueClasspathElements() {
    return classpathElements;
}

Для дальнейшего использования в случае, если вам нужно передать путь к классу ProcessBuilder:

StringBuffer buffer = new StringBuffer();
for (URL url :
    ((URLClassLoader) (Thread.currentThread()
        .getContextClassLoader())).getURLs()) {
  buffer.append(new File(url.getPath()));
  buffer.append(System.getProperty("path.separator"));
}
String classpath = buffer.toString();
int toIndex = classpath
    .lastIndexOf(System.getProperty("path.separator"));
classpath = classpath.substring(0, toIndex);
ProcessBuilder builder = new ProcessBuilder("java",
    "-classpath", classpath, "com.a.b.c.TestProgram");

Если другие ответы не работают, попробуйте это:

ClassLoader cl = ClassLoader.getSystemClassLoader();
URL[] urls = ((URLClassLoader) cl).getURLs();
for (URL url: urls) {
    System.out.println(url.getFile());
}

Перетащите этот код на пустую страницу JSP, чтобы просмотреть иерархию classLoader и связанные файлы, загруженные на каждом уровне.

Метод visit() ниже также может быть использован сам по себе

<%!
    public void visit(StringBuilder sb, int indent, ClassLoader classLoader) {
        if (indent > 20 || classLoader == null)
            return;
        String indentStr = new String(new char[indent]).replace("\0", "    ");
        sb.append("\n");
        sb.append(indentStr);
        sb.append(classLoader.getClass().getName());
        sb.append(":");
        if (classLoader instanceof java.net.URLClassLoader) {
            java.net.URL[] urls = ((java.net.URLClassLoader)classLoader).getURLs();
            for (java.net.URL url : urls) {
                sb.append("\n");
                sb.append(indentStr);
                sb.append(url);
            }
        }
        sb.append("\n");
        visit(sb, indent + 1, classLoader.getParent());
    }

%>

<%
StringBuilder sb = new StringBuilder();
visit(sb,1,this.getClass().getClassLoader());
%>
<pre>
<%=sb%>
</pre>
Другие вопросы по тегам