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

Путеводитель по библиотеке отражений

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

1. Введение

Библиотека Reflections работает как сканер путей к классам. Он индексирует отсканированные метаданные и позволяет нам запрашивать их во время выполнения. Он также может сохранять эту информацию, поэтому мы можем собирать и использовать ее в любой момент нашего проекта без необходимости повторного сканирования пути к классам.

В этом руководстве мы покажем, как настроить библиотеку Reflections и использовать ее в наших проектах Java.

2. Зависимость от Maven

Чтобы использовать Reflections , нам нужно включить его зависимость в наш проект:

<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.11</version>
</dependency>

Последнюю версию библиотеки мы можем найти на Maven Central .

3. Настройка отражений

Далее нам нужно настроить библиотеку. Основными элементами конфигурации являются URL-адреса и сканеры.

URL-адреса сообщают библиотеке, какие части пути к классам сканировать, тогда как сканеры — это объекты, которые сканируют заданные URL-адреса.

Если сканер не настроен, библиотека использует TypeAnnotationsScanner и SubTypesScanner по умолчанию.

3.1. Добавление URL

Мы можем настроить Reflections либо путем предоставления элементов конфигурации в качестве параметров конструктора varargs, либо с помощью объекта ConfigurationBuilder .

Например, мы можем добавить URL-адреса, создав экземпляр Reflections , используя строку , представляющую имя пакета, класс или загрузчик класса:

Reflections reflections = new Reflections("com.foreach.reflections");
Reflections reflections = new Reflections(MyClass.class);
Reflections reflections = new Reflections(MyClass.class.getClassLoader());

Более того, поскольку у Reflections есть конструктор varargs, мы можем комбинировать все вышеперечисленные типы конфигураций для его создания:

Reflections reflections = new Reflections("com.foreach.reflections", MyClass.class);

Здесь мы добавляем URL-адреса, указывая пакет и класс для сканирования.

Мы можем добиться тех же результатов, используя ConfigurationBuilder :

Reflections reflections = new Reflections(new ConfigurationBuilder()
.setUrls(ClasspathHelper.forPackage("com.foreach.reflections"))));

Вместе с методом forPackage () Classp ath Helper предоставляет другие методы, такие как forClass() и forClassLoader() , для добавления URL-адресов в конфигурацию. ``

3.2. Добавление сканеров

Библиотека Reflections поставляется со многими встроенными сканерами:

  • FieldAnnotationsScanner — ищет аннотации полей
  • MethodParameterScanner — сканирует методы/конструкторы, затем индексирует параметры и возвращает аннотации типов и параметров.
  • MethodParameterNamesScanner — проверяет методы/конструкторы, затем индексирует имена параметров .
  • TypeElementsScanner — проверяет поля и методы, затем сохраняет полное имя в качестве ключа, а элементы — в качестве значений.
  • MemberUsageScanner — сканирует использование методов/конструкторов/полей
  • TypeAnnotationsScanner — ищет аннотации времени выполнения класса.
  • SubTypesScanner — ищет суперклассы и интерфейсы класса, позволяя выполнять обратный поиск подтипов.
  • MethodAnnotationsScanner — сканирует аннотации метода.
  • ResourcesScanner — собирает все неклассовые ресурсы в коллекцию

Мы можем добавить сканеры в конфигурацию как параметры конструктора Reflections .

Например, давайте добавим первые два сканера из приведенного выше списка:

Reflections reflections = new Reflections("com.foreach.reflections"), 
new FieldAnnotationsScanner(),
new MethodParameterScanner());

Опять же, два сканера можно настроить с помощью вспомогательного класса ConfigurationBuilder :

Reflections reflections = new Reflections(new ConfigurationBuilder()
.setUrls(ClasspathHelper.forPackage("com.foreach.reflections"))
.setScanners(new FieldAnnotationsScanner(), new MethodParameterScanner()));

3.3. Добавление ExecutorService

В дополнение к URL-адресам и сканерам, Reflections дает нам возможность асинхронно сканировать путь к классам с помощью ExecutorService .

Мы можем добавить его как параметр конструктора Reflections , либо через ConfigurationBuilder :

Reflections reflections = new Reflections(new ConfigurationBuilder()
.setUrls(ClasspathHelper.forPackage("com.foreach.reflections"))
.setScanners(new SubTypesScanner(), new TypeAnnotationsScanner())
.setExecutorService(Executors.newFixedThreadPool(4)));

Другой вариант — просто вызвать метод useParallelExecutor() . Этот метод настраивает ExecutorService FixedThreadPool по умолчанию с размером, равным количеству доступных основных процессоров. ``

3.4. Добавление фильтров

Еще одним важным элементом конфигурации является фильтр. Фильтр сообщает сканерам, что включать, а что исключать при сканировании пути к классам .

В качестве иллюстрации мы можем настроить фильтр, чтобы исключить сканирование тестового пакета:

Reflections reflections = new Reflections(new ConfigurationBuilder()
.setUrls(ClasspathHelper.forPackage("com.foreach.reflections"))
.setScanners(new SubTypesScanner(), new TypeAnnotationsScanner())
.filterInputsBy(new FilterBuilder().excludePackage("com.foreach.reflections.test")));

Итак, до этого момента мы сделали краткий обзор различных элементов конфигурации Reflections . Далее мы увидим, как использовать библиотеку.

4. Запрос с использованием отражений

После вызова одного из конструкторов Reflections настроенные сканеры сканируют все предоставленные URL-адреса. Затем для каждого сканера библиотека помещает результаты в хранилища Multimap . В результате, чтобы использовать Reflections , нам нужно запросить эти хранилища, вызвав предоставленные методы запроса.

Давайте посмотрим на некоторые примеры этих методов запроса.

4.1. Подтипы

Начнем с извлечения всех сканеров, предоставляемых Reflections :

public Set<Class<? extends Scanner>> getReflectionsSubTypes() {
Reflections reflections = new Reflections(
"org.reflections", new SubTypesScanner());
return reflections.getSubTypesOf(Scanner.class);
}

4.2. Аннотированные типы

Далее мы можем получить все классы и интерфейсы, реализующие данную аннотацию.

Итак, давайте получим все функциональные интерфейсы пакета java.util.function :

public Set<Class<?>> getJDKFunctinalInterfaces() {
Reflections reflections = new Reflections("java.util.function",
new TypeAnnotationsScanner());
return reflections.getTypesAnnotatedWith(FunctionalInterface.class);
}

4.3. Аннотированные методы

Теперь давайте воспользуемся MethodAnnotationsScanner , чтобы получить все методы, аннотированные заданной аннотацией:

public Set<Method> getDateDeprecatedMethods() {
Reflections reflections = new Reflections(
"java.util.Date",
new MethodAnnotationsScanner());
return reflections.getMethodsAnnotatedWith(Deprecated.class);
}

4.4. Аннотированные конструкторы

Также мы можем получить все устаревшие конструкторы:

public Set<Constructor> getDateDeprecatedConstructors() {
Reflections reflections = new Reflections(
"java.util.Date",
new MethodAnnotationsScanner());
return reflections.getConstructorsAnnotatedWith(Deprecated.class);
}

4.5. Параметры методов

Кроме того, мы можем использовать MethodParameterScanner для поиска всех методов с заданным типом параметра:

public Set<Method> getMethodsWithDateParam() {
Reflections reflections = new Reflections(
java.text.SimpleDateFormat.class,
new MethodParameterScanner());
return reflections.getMethodsMatchParams(Date.class);
}

4.6. Тип возвращаемого значения методов

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

Давайте представим, что мы хотим найти все методы SimpleDateFormat , возвращающие void :

public Set<Method> getMethodsWithVoidReturn() {
Reflections reflections = new Reflections(
"java.text.SimpleDateFormat",
new MethodParameterScanner());
return reflections.getMethodsReturn(void.class);
}

4.7. Ресурсы

Наконец, давайте используем ResourcesScanner для поиска заданного имени файла в нашем пути к классам:

public Set<String> getPomXmlPaths() {
Reflections reflections = new Reflections(new ResourcesScanner());
return reflections.getResources(Pattern.compile(".*pom\\.xml"));
}

4.8. Дополнительные методы запроса

Выше было всего лишь несколько примеров, показывающих, как использовать методы запросов Reflections . Тем не менее, есть и другие методы запросов, которые мы здесь не рассмотрели:

  • getMethodsWithAnyParamAnnotated
  • getConstructorsMatchParams
  • getConstructorsWithAnyParamAnnotated
  • getFieldsAnnotatedWith
  • getMethodParamNames
  • getConstructorParamNames
  • getFieldUsage
  • getMethodUsage
  • getConstructorUsage

5. Интеграция отражений в жизненный цикл сборки

Мы можем легко интегрировать Reflections в нашу сборку Maven с помощью gmavenplus-plugin .

Настроим его на сохранение результата сканирования в файл:

<plugin>
<groupId>org.codehaus.gmavenplus</groupId>
<artifactId>gmavenplus-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<phase>generate-resources</phase>
<goals>
<goal>execute</goal>
</goals>
<configuration>
<scripts>
<script><![CDATA[
new org.reflections.Reflections(
"com.foreach.refelections")
.save("${outputDirectory}/META-INF/reflections/reflections.xml")]]>
</script>
</scripts>
</configuration>
</execution>
</executions>
</plugin>

Позже, вызвав метод collect() , мы можем получить сохраненные результаты и сделать их доступными для дальнейшего использования без необходимости выполнять новое сканирование:

Reflections reflections
= isProduction() ? Reflections.collect() : new Reflections("com.foreach.reflections");

6. Заключение

В этой статье мы изучили библиотеку Reflections . Мы рассмотрели различные элементы конфигурации и их использование. И, наконец, мы увидели, как интегрировать Reflections в жизненный цикл сборки проекта Maven.

Как всегда, полный код доступен на GitHub .