Сканирование аннотаций

Как я уже писал, аннотации в Java это просто метки в коде, которые находятся и анализируются другим кодом. А, следовательно, недостаточно уметь просто создавать аннотации, надо научиться и находить неизвестный код, который ими аннотирован. И, к сожалению, это довольно непросто.

Подготовка

Возьмём пустой maven проект и добавим в него незамысловатую аннотацию:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface GreeterTarget {
    String value() default "world";
}

К ней создадим простой класс, на который аннотацию и навесим. Так как класс нужен только как место для использования аннотации, содержимое класса совершенно не важно и не нужно:

@GreeterTarget(value = "scanned annotation")
public class Greeter { }

Сканирование classpath

В принципе найти классы с аннотацией не так и сложно, надо всего лишь взять список классов и, с помощью метода isAnnotationPresent() проверить наличие нужной нам аннотации. Однако проблема в том, что в Java список возможных классов получить нельзя. Единственное, что можно сделать, это просканировать classpath, попытаться загрузить каждый класс и проверить у класса наличие аннотации. Код ниже показывает, насколько это некомфортабельный процесс:

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());


        }
    }
}
   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 проекту:

<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, которому указываем, с каким пакетом работать и запрашиваем у него нужные классы:

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());
    }
}

Полученный список классов так же используется для получения значений аннотации.

Скачать код примера

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *