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 .