Сканирование аннотаций Java во время выполнения [закрыто]

253

Каков наилучший способ поиска аннотированного класса по всему пути к классам?

Я делаю библиотеку и хочу разрешить пользователям аннотировать свои классы, поэтому при запуске веб-приложения мне нужно просканировать весь путь к классу для определенной аннотации.

Знаете ли вы библиотеку или средство Java для этого?

Изменить: я думаю о чем-то вроде новой функциональности для веб-служб Java EE 5 или EJB. Вы аннотируете свой класс с помощью @WebServiceили, @EJBи система находит эти классы во время загрузки, чтобы они были доступны удаленно.

Alotor
источник

Ответы:

210

Используйте org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider

API

Поставщик компонента, который сканирует путь к классам из базового пакета. Затем он применяет фильтры исключения и включения к результирующим классам для поиска кандидатов.

ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(<DO_YOU_WANT_TO_USE_DEFALT_FILTER>);

scanner.addIncludeFilter(new AnnotationTypeFilter(<TYPE_YOUR_ANNOTATION_HERE>.class));

for (BeanDefinition bd : scanner.findCandidateComponents(<TYPE_YOUR_BASE_PACKAGE_HERE>))
    System.out.println(bd.getBeanClassName());
Артур Рональд
источник
5
Спасибо за информацию. Вы также знаете, как сканировать classpath для классов, поля которых имеют пользовательские аннотации?
Javatar
6
@Javatar Использовать API отражения Java. <YOUR_CLASS> .class.getFields () Для каждого поля вызовите getAnnotation (<YOUR_ANNOTATION>)
Артур Рональд
1
ПРИМЕЧАНИЕ. Если вы делаете это в приложении Spring, Spring все равно будет оценивать и действовать на основе @Conditionalаннотаций. Таким образом, если значение класса @Conditionalвозвращает false, оно не будет возвращено findCandidateComponents, даже если оно соответствует фильтру сканера. Это бросило меня сегодня - я закончил тем, что использовал решение Джонатана ниже.
Адам Берли
1
@ArthurRonald Извините, Артур. Я имею в виду, что BeanDefinitionобъект не предоставляет способ получить класс напрямую. Кажется, самое близкое, getBeanClassNameчто возвращает полное имя класса, но точное поведение этого метода неясно. Кроме того, неясно, в каком загрузчике класса был найден класс.
Макс
3
@Max Попробуй это: Class<?> cl = Class.forName(beanDef.getBeanClassName()); farenda.com/spring/find-annotated-classes
Джеймс Уоткинс
149

И еще одно решение - Google размышления .

Быстрый обзор:

  • Решение Spring - лучший способ, если вы используете Spring. В противном случае это большая зависимость.
  • Использование ASM напрямую немного обременительно.
  • Прямое использование Java Assist также неуклюже.
  • Annovention супер легкий и удобный. Нет интеграции с Maven.
  • Google отражений тянет в коллекции Google. Индексирует все, а затем супер быстро.
Джонатан
источник
43
new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class), самое лучшее.
Запп
4
мне нужно указать имя пакета? подстановочные? что за все классы в classpath?
Солнечный день
1
Остерегайтесь того, что до сих пор нет прогресса в поддержке Java 9: github.com/ronmamo/reflections/issues/186
Вадим
библиотека org.reflections не работает прямо под Java 13 (может быть, и раньше). Первый раз, когда его называют, кажется, все в порядке. последующие экземпляры и использование не дают результатов, говоря, что URL-адреса поиска не настроены.
Евво
44

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

Люк Хатчисон
источник
1
Нужна ли Java 8 для запуска?
Дэвид Джордж
1
Обновлен для использования Java7, нет проблем. Просто удалите аннотации и конвертируйте функции для использования анонимных внутренних классов. Мне нравится стиль 1 файла. Код хороший, чистый, поэтому, несмотря на то, что он не поддерживает несколько вещей, которые мне бы хотелось (класс + аннотация одновременно), я думаю, что это было бы чертовски легко добавить. Отличная работа! Если кто-то не может выполнить работу по модификации для v7, он, вероятно, должен пойти на это Reflections. Также, если вы используете guava / etc и хотите изменить коллекции, просто как пирог. Замечательные комментарии внутри тоже.
Эндрю Бакер
2
@ Александр спасибо, вы должны проверить ClassGraph, он значительно улучшен по сравнению с FastClasspathScanner.
Люк Хатчисон
2
@AndrewBacker ClassGraph (новая версия FastClasspathScanner) полностью поддерживает логические операции с помощью фильтров или операций над множествами. Смотрите примеры кода здесь: github.com/classgraph/classgraph/wiki/Code-examples
Люк Хатчисон,
1
@Luke Hutchison Уже использую ClassGraph. Помог мне с переходом на Java 10. Действительно полезная библиотека.
Александрос
25

Если вы хотите действительно легкий вес (без зависимостей, простой API, файл jar 15 Кб) и очень быстрое решение, взгляните на annotation-detectorстраницу https://github.com/rmuller/infomas-asl.

Отказ от ответственности: я автор.

rmuller
источник
20

Вы можете использовать Java Pluggable Annotation Processing API для написания процессора аннотаций, который будет выполняться во время процесса компиляции и будет собирать все аннотированные классы и создавать индексный файл для использования во время выполнения.

Это самый быстрый способ сделать аннотированное обнаружение классов, потому что вам не нужно сканировать путь к классам во время выполнения, что обычно является очень медленной операцией. Также этот подход работает с любым загрузчиком классов, а не только с URLClassLoaders, обычно поддерживаемыми сканерами времени выполнения.

Вышеуказанный механизм уже реализован в ClassIndex библиотеке .

Чтобы использовать его, аннотируйте свою пользовательскую аннотацию с помощью @IndexAnnotated meta-annotation. Во время компиляции будет создан индексный файл: META-INF / annotations / com / test / YourCustomAnnotation, в котором перечислены все аннотированные классы. Вы можете получить доступ к индексу во время выполнения, выполнив:

ClassIndex.getAnnotated(com.test.YourCustomAnnotation.class)
Sławek
источник
5

Это слишком поздно, чтобы ответить. Я бы сказал, что лучше пойти по таким библиотекам, как ClassPathScanningCandidateComponentProvider или как Scannotations

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

public class ElementScanner {

public void scanElements(){
    try {
    //Get the package name from configuration file
    String packageName = readConfig();

    //Load the classLoader which loads this class.
    ClassLoader classLoader = getClass().getClassLoader();

    //Change the package structure to directory structure
    String packagePath  = packageName.replace('.', '/');
    URL urls = classLoader.getResource(packagePath);

    //Get all the class files in the specified URL Path.
    File folder = new File(urls.getPath());
    File[] classes = folder.listFiles();

    int size = classes.length;
    List<Class<?>> classList = new ArrayList<Class<?>>();

    for(int i=0;i<size;i++){
        int index = classes[i].getName().indexOf(".");
        String className = classes[i].getName().substring(0, index);
        String classNamePath = packageName+"."+className;
        Class<?> repoClass;
        repoClass = Class.forName(classNamePath);
        Annotation[] annotations = repoClass.getAnnotations();
        for(int j =0;j<annotations.length;j++){
            System.out.println("Annotation in class "+repoClass.getName()+ " is "+annotations[j].annotationType().getName());
        }
        classList.add(repoClass);
    }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

/**
 * Unmarshall the configuration file
 * @return
 */
public String readConfig(){
    try{
        URL url = getClass().getClassLoader().getResource("WEB-INF/config.xml");
        JAXBContext jContext = JAXBContext.newInstance(RepositoryConfig.class);
         Unmarshaller um =  jContext.createUnmarshaller();
         RepositoryConfig rc = (RepositoryConfig) um.unmarshal(new File(url.getFile()));
         return rc.getRepository().getPackageName();
        }catch(Exception e){
            e.printStackTrace();
        }
    return null;

}
}

А в файле конфигурации вы помещаете имя пакета и разбираете его в классе.

kenobiwan
источник
3

С помощью Spring вы также можете просто написать следующее, используя класс AnnotationUtils. то есть:

Class<?> clazz = AnnotationUtils.findAnnotationDeclaringClass(Target.class, null);

Для получения дополнительной информации и различных методов ознакомьтесь с официальными документами: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/annotation/AnnotationUtils.html.

magiccrafter
источник
4
Хорошая идея, но если вы поместите nullзначение в качестве второго параметра (который определяет класс, в котором Spring наследует иерархию наследования для поиска класса, который использует аннотацию), вы все равно получите nullответ в соответствии с реализацией.
jonashackt
3

Zapp замечательно комментирует все эти ответы:

new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class)
Зон
источник
2

В Classloader API нет метода «перечисления», потому что загрузка классов - это действие «по требованию» - у вас обычно есть тысячи классов в вашем пути к классам, только часть из которых когда-либо понадобится (rt.jar только 48MB в наше время!).

Так что, даже если бы вы могли перечислить все классы, это заняло бы много времени и памяти.

Простой подход состоит в том, чтобы перечислить соответствующие классы в установочном файле (xml или любой другой, который вам подходит); если вы хотите сделать это автоматически, ограничьте себя одним JAR или одним каталогом классов.

MFX
источник
2

Google Reflection если вы хотите открыть интерфейсы.

Весна ClassPathScanningCandidateComponentProviderне открывает интерфейсы.

madhu_karnati
источник
1

Google Reflections кажется намного быстрее, чем Spring. Нашел этот запрос функции, который устраняет эту разницу: http://www.opensaga.org/jira/browse/OS-738

Это причина использовать Reflections, так как время запуска моего приложения действительно важно во время разработки. Отражения, кажется, также очень просты в использовании для моего случая использования (найти все реализации интерфейса).

Мартин Обеле
источник
1
Если вы посмотрите на проблему JIRA, там есть комментарии, что они отошли от Reflections из-за проблем со стабильностью.
Вим Deblauwe
1

Если вы ищете альтернативу отражениям, я бы рекомендовал Panda Utilities - AnnotationsScanner . Это сканер без гуавы (Гуава имеет ~ 3 МБ, Panda Utilities имеет ~ 200 КБ) сканер, основанный на исходном коде библиотеки отражений.

Он также предназначен для будущих поисков. Если вы хотите сканировать несколько раз включенные источники или даже предоставить API, который позволяет кому-то сканировать текущий путь к классу, AnnotationsScannerProcessкэширует все извлеченные ClassFiles, так что это действительно быстро.

Простой пример AnnotationsScannerиспользования:

AnnotationsScanner scanner = AnnotationsScanner.createScanner()
        .includeSources(ExampleApplication.class)
        .build();

AnnotationsScannerProcess process = scanner.createWorker()
        .addDefaultProjectFilters("net.dzikoysk")
        .fetch();

Set<Class<?>> classes = process.createSelector()
        .selectTypesAnnotatedWith(AnnotationTest.class);
dzikoysk
источник
1

Весна имеет то, что называется AnnotatedTypeScannerклассом.
Этот класс внутренне использует

ClassPathScanningCandidateComponentProvider

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

Можно просто расширить этот класс или использовать тот же класс для сканирования. Ниже приведено определение конструктора.

/**
     * Creates a new {@link AnnotatedTypeScanner} for the given annotation types.
     * 
     * @param considerInterfaces whether to consider interfaces as well.
     * @param annotationTypes the annotations to scan for.
     */
    public AnnotatedTypeScanner(boolean considerInterfaces, Class<? extends Annotation>... annotationTypes) {

        this.annotationTypess = Arrays.asList(annotationTypes);
        this.considerInterfaces = considerInterfaces;
    }
swayamraina
источник