Перейти к основному содержимому

Список всех классов, загруженных в определенный загрузчик классов

· 4 мин. чтения

1. Обзор

В этом учебнике мы проанализируем технику составления списка всех классов, загружаемых определенным загрузчиком классов в Java, с использованием API инструментария Java `` . Мы также увидим, как создать и загрузить агент Java для получения экземпляра Instrumentation и вызвать необходимые методы для выполнения нашей задачи.

2. Загрузчики классов в Java

Загрузчики классов являются неотъемлемой частью JRE (Java Runtime Environment). Их работа заключается в динамической загрузке классов в виртуальную машину Java . Другими словами, они загружают классы в память по требованию, когда этого требует приложение. В статье о загрузчиках классов Java рассказывается об их различных типах и подробно рассказывается, как они работают.

3. Использование инструментального API

Интерфейс Instrumentation предоставляет метод getInitiatedClasses(загрузчик классов) , который можно вызывать для возврата массива, содержащего все классы, загруженные конкретным загрузчиком . Давайте посмотрим, как это работает.

Во-первых, нам нужно создать и загрузить агент для получения экземпляра интерфейса Instrumentation . Агент Java — это инструмент для инструментовки программ, работающих на JVM (виртуальная машина Java).

Другими словами, он может добавлять или изменять байт-код методов для сбора данных. Нам потребуется, чтобы агент получил дескриптор экземпляра Instrumentation и вызвал требуемый метод.

Существует несколько способов создания и загрузки агента . В этом руководстве мы будем использовать метод статической загрузки с использованием метода premain и параметра -javaagent .

3.1. Создание агента Java

Чтобы создать агент Java, нам нужно определить метод premain , которому экземпляр Instrumentation будет передан при загрузке агента . Давайте теперь создадим класс ListLoadedClassesAgent :

public class ListLoadedClassesAgent {

private static Instrumentation instrumentation;

public static void premain(String agentArgs, Instrumentation instrumentation) {
ListLoadedClassesAgent.instrumentation = instrumentation;
}
}

3.2. Определение методов listLoadedClasses

В дополнение к определению агента мы определим и предоставим статический метод для возврата массива загруженных классов для данного загрузчика классов.

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

Давайте посмотрим код в действии:

public static Class<?>[] listLoadedClasses(String classLoaderType) {
return instrumentation.getInitiatedClasses(
getClassLoader(classLoaderType));
}

private static ClassLoader getClassLoader(String classLoaderType) {
ClassLoader classLoader = null;
switch (classLoaderType) {
case "SYSTEM":
classLoader = ClassLoader.getSystemClassLoader();
break;
case "EXTENSION":
classLoader = ClassLoader.getSystemClassLoader().getParent();
break;
case "BOOTSTRAP":
break;
default:
break;
}
return classLoader;
}

Обратите внимание: если мы используем Java 9 или выше, мы можем использовать метод getPlatformClassLoader . В нем будут перечислены классы, загруженные загрузчиком классов платформы. В этом случае корпус переключателя также будет содержать:

case "PLATFORM":
classLoader = ClassLoader.getPlatformClassLoader();
break;

3.3. Создание файла манифеста агента

Теперь давайте создадим файл манифеста MANIFEST.MF с соответствующими атрибутами для запуска нашего агента, включая:

Premain-Class: com.foreach.loadedclasslisting.ListLoadedClassesAgent

Полный список атрибутов манифеста для JAR-файла агента доступен в официальной документации пакета java.lang.instrument .

3.4. Загрузка агента и запуск приложения

Давайте теперь загрузим агент и запустим приложение. Во-первых, нам нужен JAR-файл агента с файлом манифеста, содержащим информацию о Premain-Class . Кроме того, нам нужен JAR-файл приложения с файлом манифеста, содержащим информацию об основном классе . Класс Launcher, содержащий метод main , запустит наше приложение. Затем мы сможем распечатать классы, загруженные различными типами загрузчиков классов:

public class Launcher {

public static void main(String[] args) {
printClassesLoadedBy("BOOTSTRAP");
printClassesLoadedBy("SYSTEM");
printClassesLoadedBy("EXTENSION");
}

private static void printClassesLoadedBy(String classLoaderType) {
System.out.println(classLoaderType + " ClassLoader : ");
Class<?>[] classes = ListLoadedClassesAgent.listLoadedClasses(classLoaderType);
Arrays.asList(classes)
.forEach(clazz -> System.out.println(clazz.getCanonicalName()));
}
}

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

java -javaagent:agent.jar -jar app.jar

После запуска вышеуказанной команды мы увидим вывод:

BOOTSTRAP ClassLoader :
java.lang.ClassValue.Entry[]
java.util.concurrent.ConcurrentHashMap.Segment
java.util.concurrent.ConcurrentHashMap.Segment[]
java.util.StringTokenizer
..............
SYSTEM ClassLoader :
java.lang.Object[]
java.lang.Object[][]
java.lang.Class
java.lang.Class[]
..............
EXTENSION ClassLoader :
byte[]
char[]
int[]
int[][]
short[]

4. Вывод

В этом руководстве мы узнали о методе перечисления всех классов, загруженных в определенный загрузчик классов.

Во-первых, мы создали агент Java. После этого мы определили метод для вывода списка загруженных классов с помощью Java Instrumentation API. Наконец, мы создали файлы манифеста агента, загрузили агент и запустили наше приложение.

Как всегда, полный исходный код примера можно найти на GitHub .