1. Обзор
В этом кратком руководстве мы поговорим о библиотеке Classgraph — в чем она помогает и как мы можем ее использовать.
Classgraph помогает нам находить целевые ресурсы в пути к классам Java, создает метаданные о найденных ресурсах и предоставляет удобные API для работы с метаданными.
Этот вариант использования очень популярен в приложениях на основе Spring, где компоненты, отмеченные стереотипными аннотациями, автоматически регистрируются в контексте приложения. Однако мы можем использовать этот подход и для пользовательских задач. Например, нам может понадобиться найти все классы с определенной аннотацией или все файлы ресурсов с определенным именем.
Круто то, что Classgraph работает быстро, так как работает на уровне байт-кода , то есть проверяемые классы не загружаются в JVM и не использует отражение для обработки.
2. Зависимости Maven
Во-первых, давайте добавим библиотеку classgraph в наш pom.xml
:
<dependency>
<groupId>io.github.classgraph</groupId>
<artifactId>classgraph</artifactId>
<version>4.8.28</version>
</dependency>
В следующих разделах мы рассмотрим несколько практических примеров с 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());
}
Разберем приведенный выше пример:
- мы начали с настройки параметров сканирования (мы настроили сканер для анализа только информации о классе и аннотации, а также дали ему указание анализировать только файлы из целевого пакета)
- мы выполнили сканирование , используя метод ClassGraph.scan
()
- мы использовали
ScanResult
для поиска аннотированных классов, вызвав метод getClassWithAnnotation()
Как мы также увидим в следующих примерах, объект ScanResult
может содержать много информации об API, которые мы хотим проверить, например ClassInfoList.
4. Фильтрация по аннотации метода
Давайте расширим наш пример до аннотаций методов:
public class MethodWithAnnotation {
@TestAnnotation
public void service() {
}
}
Мы можем найти все классы, у которых есть методы, отмеченные целевой аннотацией, с помощью аналогичного метода — getClassesWithMethodAnnotations()
:
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());
}
Метод возвращает объект ClassInfoList
, содержащий информацию о классах, соответствующих сканированию.
5. Фильтрация по параметру аннотации
Давайте также посмотрим, как мы можем найти все классы с методами, отмеченными целевой аннотацией, и со значением параметра целевой аннотации.
Во-первых, давайте определим классы, содержащие методы с @TestAnnotation,
с двумя разными значениями параметров:
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());
}
Здесь мы использовали классы метаданных AnnotationInfo
и MethodInfo
, чтобы найти метаданные о методах и аннотациях, которые мы хотим проверить.
6. Фильтрация по аннотации поля
Мы также можем использовать метод getClassesWithFieldAnnotation()
для фильтрации результата ClassInfoList
на основе аннотаций полей:
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. Поиск ресурсов
Наконец, мы посмотрим, как мы можем найти информацию о ресурсах пути к классам.
Давайте создадим файл ресурсов в корневом каталоге пути к классам classgraph
— например, src/test/resources/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");
}
Здесь мы видим, что использовали метод
getResourcesWithExtension () ScanResult
для поиска нашего конкретного файла. У класса есть несколько других полезных методов, связанных с ресурсами, таких как getAllResources(), getResourcesWithPath()
и getResourcesMatchingPattern() .
Эти методы возвращают объект ResourceList
, который в дальнейшем можно использовать для перебора объектов Resource и управления ими.
8. Создание экземпляра
Когда мы хотим создавать экземпляры найденных классов, очень важно делать это не через Class.forName,
а с помощью библиотечного метода ClassInfo.loadClass
.
Причина в том, что Classgraph использует собственный загрузчик классов для загрузки классов из некоторых файлов JAR. Итак, если мы используем Class.forName
, один и тот же класс может быть загружен более одного раза разными загрузчиками классов, и это может привести к нетривиальным ошибкам.
9. Заключение
В этой статье мы узнали, как эффективно находить ресурсы пути к классам и проверять их содержимое с помощью библиотеки Classgraph.
Как обычно, полный исходный код этой статьи доступен на GitHub .