Как я уже писал, аннотации в 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()); } }
Полученный список классов так же используется для получения значений аннотации.