1. Обзор
В этом кратком руководстве мы поговорим о библиотеке Classgraph — в чем она помогает и как мы можем ее использовать.
График классов помогает нам находить целевые ресурсы в пути к классам Java, создает метаданные о найденных ресурсах и предоставляет удобные API для работы с метаданными.
Этот вариант использования очень популярен в приложениях на базе Spring, где компоненты, помеченные аннотациями стереотипов, автоматически регистрируются в контексте приложения. Однако мы можем использовать этот подход и для пользовательских задач. Например, мы можем захотеть найти все классы с определенной аннотацией или все файлы ресурсов с определенным именем.
Самое интересное, что График классов быстр , так как он работает на уровне байт-кода , что означает, что проверенные классы не загружаются в JVM и не используют отражение для обработки.
2. Зависимости Maven
Во-первых, давайте добавим график классов библиотеку в ваш pom.xml :
io.github.classgraph classgraph 4.8.28
В следующих разделах мы рассмотрим несколько практических примеров использования API библиотеки.
3. Основное использование
Существует три основных шага для использования библиотеки:
- Настройка параметров сканирования – например, целевого пакета(ов)
- Выполните сканирование
- Работа с результатами сканирования
Давайте создадим следующий домен для нашего примера настройки:
@Target({TYPE, METHOD, FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface TestAnnotation { String value() default ""; }
@TestAnnotation public class ClassWithAnnotation { }
Теперь давайте рассмотрим 3 шага выше на примере поиска классов с помощью @TestAnnotation :
try (ScanResult result = new ClassGraph().enableClassInfo().enableAnnotationInfo() .whitelistPackages(getClass().getPackage().getName()).scan()) { ClassInfoList classInfos = result.getClassesWithAnnotation(TestAnnotation.class.getName()); assertThat(classInfos).extracting(ClassInfo::getName).contains(ClassWithAnnotation.class.getName()); }
Давайте разберем приведенный выше пример:
- мы начали с настройки параметров сканирования (мы настроили сканер для анализа только информации о классах и аннотациях, а также инструктировали его анализировать только файлы из целевого пакета)
- мы выполнили сканирование с помощью метода Class Graph.scan()
- мы использовали Результат сканирования для поиска аннотированных классов, вызвав метод get Class With Annotation()
Как мы также увидим в следующих примерах, объект Результат сканирования может содержать много информации об API, которые мы хотим проверить, например ClassInfoList.
4. Фильтрация по Аннотации метода
Давайте расширим наш пример до аннотаций методов:
public class MethodWithAnnotation { @TestAnnotation public void service() { } }
Мы можем найти все классы, у которых есть методы, отмеченные целевой аннотацией, используя аналогичный метод — получить Классы С Аннотациями Методов() :
try (ScanResult result = new ClassGraph().enableAllInfo() .whitelistPackages(getClass().getPackage().getName()).scan()) { ClassInfoList classInfos = result.getClassesWithMethodAnnotation(TestAnnotation.class.getName()); assertThat(classInfos).extracting(ClassInfo::getName).contains(MethodWithAnnotation.class.getName()); }
Метод возвращает КлассИфикАтор объект, содержащий информацию о классах, соответствующих сканированию.
5. Фильтрация по параметру Аннотации
Давайте также посмотрим, как мы можем найти все классы с методами, отмеченными целевой аннотацией, и со значением параметра целевой аннотации.
Во-первых, давайте определим классы, содержащие методы с @TestAnnotation, с 2 различными значениями параметров:
public class MethodWithAnnotationParameterDao { @TestAnnotation("dao") public void service() { } }
public class MethodWithAnnotationParameterWeb { @TestAnnotation("web") public void service() { } }
Теперь давайте пройдемся по ClassInfoList результату и проверим аннотации каждого метода:
try (ScanResult result = new ClassGraph().enableAllInfo() .whitelistPackages(getClass().getPackage().getName()).scan()) { ClassInfoList classInfos = result.getClassesWithMethodAnnotation(TestAnnotation.class.getName()); ClassInfoList webClassInfos = classInfos.filter(classInfo -> { return classInfo.getMethodInfo().stream().anyMatch(methodInfo -> { AnnotationInfo annotationInfo = methodInfo.getAnnotationInfo(TestAnnotation.class.getName()); if (annotationInfo == null) { return false; } return "web".equals(annotationInfo.getParameterValues().getValue("value")); }); }); assertThat(webClassInfos).extracting(ClassInfo::getName) .contains(MethodWithAnnotationParameterWeb.class.getName()); }
Здесь мы использовали Аннотацияинфо и Методинфо классы метаданных для поиска метаданных о методах и аннотациях, которые мы хотим проверить.
6. Фильтрация по аннотации поля
Мы также можем использовать метод get Classes With Field Annotation() для фильтрации Списка сведений о классе результата на основе аннотаций полей:
public class FieldWithAnnotation { @TestAnnotation private String s; }
try (ScanResult result = new ClassGraph().enableAllInfo() .whitelistPackages(getClass().getPackage().getName()).scan()) { ClassInfoList classInfos = result.getClassesWithFieldAnnotation(TestAnnotation.class.getName()); assertThat(classInfos).extracting(ClassInfo::getName).contains(FieldWithAnnotation.class.getName()); }
7. Поиск Ресурсов
Наконец, мы рассмотрим, как мы можем найти информацию о ресурсах classpath.
Давайте создадим файл ресурсов в корневом каталоге class graph classpath — например, src/test/ресурсы/classgraph/my.config — и предоставим ему некоторое содержимое:
my data
Теперь мы можем найти ресурс и получить его содержимое:
try (ScanResult result = new ClassGraph().whitelistPaths("classgraph").scan()) { ResourceList resources = result.getResourcesWithExtension("config"); assertThat(resources).extracting(Resource::getPath).containsOnly("classgraph/my.config"); assertThat(resources.get(0).getContentAsString()).isEqualTo("my data"); }
Здесь мы видим, что мы использовали метод Результат сканирования’ s получить ресурсы с расширением() для поиска вашего конкретного файла. В классе есть несколько других полезных методов, связанных с ресурсами, таких как получить все ресурсы (), получить ресурсы с помощью пути() и | getResourcesMatchingPattern() .
Эти методы возвращают Список ресурсов объект, который в дальнейшем можно использовать для перебора объектов Ресурсов и управления ими.
8. Создание экземпляра
Когда мы хотим создать экземпляр найденных классов, очень важно делать это не через Class.forName, но с помощью библиотечного метода ClassInfo.loadClass .
Причина в том, что Class graph использует свой собственный загрузчик классов для загрузки классов из некоторых файлов JAR. Таким образом, если мы используем Class.forName , один и тот же класс может быть загружен более одного раза разными загрузчиками классов, и это может привести к нетривиальным ошибкам.
9. Заключение
В этой статье мы узнали, как эффективно находить ресурсы пути к классам и проверять их содержимое с помощью библиотеки графиков классов.
Как обычно, полный исходный код этой статьи доступен на GitHub .