Сканування анотацій на Java під час виконання [закрито]


253

Який найкращий спосіб пошуку по всьому класові для позначеного класу?

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

Чи знаєте ви бібліотеку чи Java-сервіс для цього?

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

Відповіді:


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
Дякуємо за інформацію. Ви також знаєте, як сканувати класний шлях для класів, поля яких мають спеціальну анотацію?
Джаватар

6
@Javatar Використовуйте API відображення Java. <YOUR_CLASS> .class.getFields () Для кожного поля виберіть getAnnotation (<YOUR_ANNOTATION>)
Артур Рональд,

1
ПРИМІТКА. Якщо ви робите це у додатку Spring, Spring все одно буде оцінювати та діяти на основі @Conditionalанотацій. Отже, якщо для класу @Conditionalзначення повертається помилково, воно не повернеться findCandidateComponents, навіть якщо він відповідає фільтру сканера. Це кинуло мене сьогодні - я натомість застосував рішення Джонатана нижче.
Адам Берлі

1
@ArthurRonald Вибачте, Артур. Я маю на увазі, що BeanDefinitionоб’єкт не забезпечує спосіб отримати клас безпосередньо. Найближчим виглядає те, getBeanClassNameщо повертає повністю кваліфіковане ім’я класу, але точна поведінка цього методу не зрозуміла. Крім того, не ясно, в якому навантажувачі цього класу було знайдено.
Макс

3
@Max Спробуйте це: Class<?> cl = Class.forName(beanDef.getBeanClassName()); farenda.com/spring/find-annotated-classes
Джеймс Уоткінс

149

І ще одне рішення - роздуми Google .

Швидкий огляд:

  • Весняне рішення - це шлях, якщо ви використовуєте Spring. Інакше це велика залежність.
  • Використання ASM безпосередньо трохи громіздке.
  • Використання Java Assist безпосередньо також є незграбним.
  • Анонвенція - супер легка і зручна. Поки що немає інтеграції Maven.
  • Google-роздуми тягнуть у колекції Google. Індексує все, а потім дуже швидко.

43
new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class). c'est tout.
запп

4
чи потрібно вказати назву пакета? wildcard? що для всіх класів на уроці?
Сонячний день


бібліотека org.reflections не працює прямо під Java 13 (можливо, і раніше). Перший раз, коли його називають, здається, що це нормально. наступні моменти та використання не вдається сказати, що пошукові URL-адреси не налаштовані.
Євво

44

Ви можете знайти класи з будь-якою анотацією за допомогою ClassGraph , а також шукати інші критерії, що цікавлять, наприклад, класи, які реалізують даний інтерфейс. (Відмова, я автор ClassGraph.) ClassGraph може побудувати абстрактне зображення всього графа класу (усіх класів, анотацій, методів, параметрів методу та полів) у пам'яті, для всіх класів на класі, або для класів у пакети з білими списками, і ви можете запитувати цей графік класу, як тільки захочете. ClassGraph підтримує більше механізмів специфікації classload і завантажувачів, ніж будь-який інший сканер, а також працює безперебійно з новою системою модулів JPMS, тому якщо ви базуєте свій код на ClassGraph, ваш код буде максимально портативним. Дивіться API тут.


1
Чи потрібна для запуску Java 8?
Девід Джордж

1
Оновлено для використання Java7, немає проблем. Просто видаліть примітки та перетворіть функції для використання анонімних внутрішніх класів. Мені подобається стиль 1 файлу. Код є приємним чистим, тому, хоча він не підтримує декількох речей, які я хотів би (клас + анотація одночасно), я думаю, що це було б досить чортово легко додати. Чудова робота! Якщо хтось не може встигнути виконати роботу з модифікації для v7, він, ймовірно, повинен перейти Reflections. Крім того, якщо ви використовуєте гуаву / тощо і хочете змінити колекції, простий як пиріг. Чудові коментарі теж.
Ендрю Беккер

2
@ Александрос спасибі, ви повинні перевірити ClassGraph, він значно покращився порівняно з FastClasspathScanner.
Люк Хатчісон

2
@AndrewBacker ClassGraph (нова версія FastClasspathScanner) має повну підтримку булевих операцій, через фільтри або встановлені операції. Дивіться приклади коду тут: github.com/classgraph/classgraph/wiki/Code-examples
Люк Хатчісон

1
@Luke Hutchison Вже використовує ClassGraph. Допоміг мені з переходом на Java 10. Дійсно корисна бібліотека.
Александрос

24

Якщо ви хочете по-справжньому легкої ваги (відсутність залежностей, простий API, 15-футовий файл банку) та дуже швидке рішення, погляньте на annotation-detectorзнайдене за адресою https://github.com/rmuller/infomas-asl

Відмова: Я - автор.


20

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

Це найшвидший спосіб зробити анотоване відкриття класу, оскільки вам не потрібно сканувати свій класний шлях під час виконання, як правило, це дуже повільна робота. Також такий підхід працює з будь-яким завантажувачем класів, а не лише з URLClassLoaders, зазвичай підтримуваними сканерами виконання.

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

Щоб використовувати це, анотуйте вашу власну анотацію за допомогою @IndexAnnotated мета-анотації. Це створить під час компіляції файл індексу: META-INF / анотації / com / test / YourCustomAnnotation із переліком усіх анотованих класів. Ви можете отримати доступ до індексу під час виконання, виконавши:

ClassIndex.getAnnotated(com.test.YourCustomAnnotation.class)

5

Чи вже пізно відповідати. Я б сказав, краще поїхати до бібліотек типу ClassPathScanningCandidateComponentProvider або подібних Сканотацій

Але навіть після того, як хтось захоче спробувати деякі руки з 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;

}
}

І в config File ви поміщаєте ім'я пакету і знімаєте його з класом.


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


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

3

Є чудовий коментар zapp, який занурюється у всі відповіді:

new Reflections("my.package").getTypesAnnotatedWith(MyAnnotation.class)

2

API Classloader не має методу "перерахування", оскільки завантаження класів - це діяльність "на вимогу" - у вас зазвичай є тисячі класів на вашому класі, лише частина яких коли-небудь знадобиться (rt.jar Один зараз - це 48 Мб!).

Отже, навіть якби ти міг перерахувати всі класи, це забирає багато часу та пам'яті.

Простий підхід полягає в тому, щоб перелічити відповідні класи у файлі налаштування (xml або що завгодно); якщо ви хочете зробити це автоматично, обмежте себе одним JAR або одним каталогом класів.


2

Google Reflection якщо ви також хочете виявити інтерфейси.

Весна ClassPathScanningCandidateComponentProviderне знаходить інтерфейсів.


1

Google Роздуми здаються набагато швидшими, ніж Весна. Знайдено цей запит на функцію, який вирішує цю різницю: http://www.opensaga.org/jira/browse/OS-738

Це причина використовувати Reflections, оскільки час запуску моєї програми дуже важливий під час розробки. Роздуми також здаються дуже простими у використанні для мого випадку використання (знайдіть усіх реалізаторів інтерфейсу).


1
Якщо ви подивитесь на питання JIRA, там є коментарі, що вони відійшли від роздумів через проблеми зі стабільністю.
Вім Деблауве

1

Якщо ви шукаєте альтернативу роздумам, я хотів би порекомендувати Panda Utilities - AnnotationsScanner . Це сканер без гуави (у Guava ~ 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);

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;
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.