Как я уже писал, аннотации в Java это просто метки в коде, которые находятся и анализируются другим кодом. А, следовательно, недостаточно уметь просто создавать аннотации, надо научиться и находить неизвестный код, который ими аннотирован. И, к сожалению, это довольно непросто.
Подготовка
Возьмём пустой maven проект и добавим в него незамысловатую аннотацию:
1
2
3
4
5
|
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface GreeterTarget {
String value() default "world";
}
|
К ней создадим простой класс, на который аннотацию и навесим. Так как класс нужен только как место для использования аннотации, содержимое класса совершенно не важно и не нужно:
1
2
|
@GreeterTarget(value = "scanned annotation")
public class Greeter { }
|
Сканирование classpath
В принципе найти классы с аннотацией не так и сложно, надо всего лишь взять список классов и, с помощью метода isAnnotationPresent() проверить наличие нужной нам аннотации. Однако проблема в том, что в Java список возможных классов получить нельзя. Единственное, что можно сделать, это просканировать classpath, попытаться загрузить каждый класс и проверить у класса наличие аннотации. Код ниже показывает, насколько это некомфортабельный процесс:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
private static void manualScan() throws IOException, URISyntaxException {
String packageName = Main.class.getPackage().getName();
String path = packageName.replace(".", "/");
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Enumeration<URL> resources = classLoader.getResources(path);
Iterable<URL> urls = resources::asIterator;
List<File> dirs = new ArrayList<>();
for (URL url: urls) {
dirs.add(new File(url.toURI().getPath()));
}
List<Class> classes = dirs.stream().flatMap((File d) -> findClasses(d, packageName).stream()).collect(Collectors.toList());
for(Class cls: classes) {
if (cls.isAnnotationPresent(GreeterTarget.class)) {
GreeterTarget target = (GreeterTarget) cls.getAnnotation(GreeterTarget.class);
System.out.println(target.value());
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
private static List<Class> findClasses(File directory, String packageName) {
if (!directory.exists()) {
return Collections.emptyList();
}
List<Class> classes = new ArrayList<>();
File[] files = directory.listFiles();
if (files == null) {
return Collections.emptyList();
}
for (File file : files) {
if (file.isDirectory()) {
classes.addAll(findClasses(file, packageName + "." + file.getName()));
}
else if (file.getName().endsWith(".class")) {
try {
classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
} catch (ClassNotFoundException e) {
//Skip, as we are searching for the classes
}
}
}
return classes;
}
|
Строки 2-3 функции manualScan() получают имя пакета для текущего класса. Мы будем загружать классы только из этого пакета, чтобы не пробовать все доступные классы JRE. В строках 5-8 у текущего class loader запрашивается список путей, входящих в classpath. Наконец в 14 строке этот список преобразуется функцией findClasses, которая просматривает каждый каталог из classpath на наличие файлов с суффиксом .class и пытается их загрузить. Так же эта функция рекурсивно вызывает саму себя, наткнувшись на каталог. И вся эта машинерия нужна только для того, чтобы загрузить список классов! Более того, конкретно этот код будет работать только в очень простых случаях и не справится даже с jar файлом 🙁
Наконец, в строках 15-21, проверяется, есть ли у класса нужна аннотация и, если она есть, её значение достаётся из класса и печатается. Именно в этом месте вы можете вставить собственную логику работы с аннотацией.
Используем Reflections
К счастью в мире java есть более 9000 библиотек на все случаи жизни, есть и библиотека для сканирования классов на аннотации (и не только). Добавим библиотеку Reflectlions к нашему maven проекту:
1
2
3
4
5
6
7
8
9
10
11
12
|
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<reflections.version>0.9.11</reflections.version>
</properties>
<dependencies>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>${reflections.version}</version>
</dependency>
</dependencies>
|
Сканирование классов сводится к двум вызовам — создаём объект Reflections, которому указываем, с каким пакетом работать и запрашиваем у него нужные классы:
1
2
3
4
5
6
7
8
9
|
private static void reflectionsScan() {
Reflections reflections = new Reflections(Main.class.getPackage().getName());
Set<Class<?>> classes = reflections.getTypesAnnotatedWith(GreeterTarget.class);
for(Class cls: classes) {
GreeterTarget target = (GreeterTarget) cls.getAnnotation(GreeterTarget.class);
System.out.println(target.value());
}
}
|
Полученный список классов так же используется для получения значений аннотации.
Код примера доступен на github