Додавання анотацій Java під час виконання


77

Чи можна додати анотацію до об'єкта (зокрема, у моєму випадку, методу) під час виконання?

Для більш детального пояснення: у мене є два модулі, moduleA та moduleB. moduleB залежить від moduleA, який ні від чого не залежить. (modA - це мої основні типи даних та інтерфейси, і таке, modB - це рівень db / даних) modB також залежить від externalLibrary. У моєму випадку modB передає клас з modA до externalLibrary, який потребує певних методів для анотовання. Конкретні анотації є частиною externalLib, і, як я вже сказав, modA не залежить від externalLib, і я хотів би зберегти його таким.

Отже, чи можливо це, чи у вас є пропозиції щодо інших способів розгляду цієї проблеми?


Перевірте, чи може це допомогти вам stackoverflow.com/a/14276270/4741746, принаймні ми можемо його змінити
Sushant Gosavi

Відповіді:


21

Неможливо додати анотацію під час виконання, схоже, вам потрібно ввести адаптер, який модуль B використовує для обтікання об’єкта з модуля A, виставляючи необхідні анотовані методи.


1
Я відстою це. Але я можу подумати про анотацію оригіналу, я не бачу тут великої проблеми. Зазвичай ми робимо це, якщо взяти до уваги сутності JPA, які ви передаєте віддаленому компоненту EJB для зберігання в БД. І ви використовуєте те саме для заповнення вашого інтерфейсу.
Adeel Ansari

Том: Ах, звичайно. Можливо, з успадкуванням: Розширити клас з модуля A, замінити розглянутий метод, а потім анотувати це?
Клейтон,

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

Отже, просто йдіть із простим. Тим не менше, було б зручно кодувати адаптер пізніше, коли б це не було часу. Як ви вже сказали, ви можете розглянути можливість успадкування для цього, тому спілкування з супер типом зробить це.
Adeel Ansari

1
Я вирішив застосувати до цього гібридний підхід. Наразі я просто анотував оригінальний метод (додаючи залежність до modA), припускаючи, що я завжди можу просто витягнути анотацію пізніше і використовувати адаптер. Спасибі люди!
Клейтон,

43

Це можливо за допомогою бібліотеки приладів байт-коду, такої як Javassist .

Зокрема, погляньте на клас AnnotationsAttribute, щоб отримати приклад того, як створювати / встановлювати анотації, та розділ підручника з API байт-коду для загальних вказівок щодо роботи з файлами класів.

Однак це все, але не просто і просто - я б НЕ рекомендував такий підхід і пропонував би замість цього розглянути відповідь Тома, якщо вам не потрібно робити це для величезної кількості класів (або зазначені класи не доступні для вас до часу виконання і, отже, написання адаптер неможливий).


27

Також можна додати Анотацію до класу Java під час виконання за допомогою API відображення Java. По суті, потрібно відтворити внутрішні карти анотацій, визначені в класі java.lang.Class(або для Java 8, визначені у внутрішньому класі java.lang.Class.AnnotationData). Природно, що такий підхід є досить хакерським і може зламатися в будь-який час для нових версій Java. Але для швидкого та брудного тестування / створення прототипів цей підхід може бути корисним часом.

Доказ концептуального прикладу для Java 8:

public final class RuntimeAnnotations {

    private static final Constructor<?> AnnotationInvocationHandler_constructor;
    private static final Constructor<?> AnnotationData_constructor;
    private static final Method Class_annotationData;
    private static final Field Class_classRedefinedCount;
    private static final Field AnnotationData_annotations;
    private static final Field AnnotationData_declaredAnotations;
    private static final Method Atomic_casAnnotationData;
    private static final Class<?> Atomic_class;

    static{
        // static initialization of necessary reflection Objects
        try {
            Class<?> AnnotationInvocationHandler_class = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            AnnotationInvocationHandler_constructor = AnnotationInvocationHandler_class.getDeclaredConstructor(new Class[]{Class.class, Map.class});
            AnnotationInvocationHandler_constructor.setAccessible(true);

            Atomic_class = Class.forName("java.lang.Class$Atomic");
            Class<?> AnnotationData_class = Class.forName("java.lang.Class$AnnotationData");

            AnnotationData_constructor = AnnotationData_class.getDeclaredConstructor(new Class[]{Map.class, Map.class, int.class});
            AnnotationData_constructor.setAccessible(true);
            Class_annotationData = Class.class.getDeclaredMethod("annotationData");
            Class_annotationData.setAccessible(true);

            Class_classRedefinedCount= Class.class.getDeclaredField("classRedefinedCount");
            Class_classRedefinedCount.setAccessible(true);

            AnnotationData_annotations = AnnotationData_class.getDeclaredField("annotations");
            AnnotationData_annotations.setAccessible(true);
            AnnotationData_declaredAnotations = AnnotationData_class.getDeclaredField("declaredAnnotations");
            AnnotationData_declaredAnotations.setAccessible(true);

            Atomic_casAnnotationData = Atomic_class.getDeclaredMethod("casAnnotationData", Class.class, AnnotationData_class, AnnotationData_class);
            Atomic_casAnnotationData.setAccessible(true);

        } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | NoSuchFieldException e) {
            throw new IllegalStateException(e);
        }
    }

    public static <T extends Annotation> void putAnnotation(Class<?> c, Class<T> annotationClass, Map<String, Object> valuesMap){
        putAnnotation(c, annotationClass, annotationForMap(annotationClass, valuesMap));
    }

    public static <T extends Annotation> void putAnnotation(Class<?> c, Class<T> annotationClass, T annotation){
        try {
            while (true) { // retry loop
                int classRedefinedCount = Class_classRedefinedCount.getInt(c);
                Object /*AnnotationData*/ annotationData = Class_annotationData.invoke(c);
                // null or stale annotationData -> optimistically create new instance
                Object newAnnotationData = createAnnotationData(c, annotationData, annotationClass, annotation, classRedefinedCount);
                // try to install it
                if ((boolean) Atomic_casAnnotationData.invoke(Atomic_class, c, annotationData, newAnnotationData)) {
                    // successfully installed new AnnotationData
                    break;
                }
            }
        } catch(IllegalArgumentException | IllegalAccessException | InvocationTargetException | InstantiationException e){
            throw new IllegalStateException(e);
        }

    }

    @SuppressWarnings("unchecked")
    private static <T extends Annotation> Object /*AnnotationData*/ createAnnotationData(Class<?> c, Object /*AnnotationData*/ annotationData, Class<T> annotationClass, T annotation, int classRedefinedCount) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) AnnotationData_annotations.get(annotationData);
        Map<Class<? extends Annotation>, Annotation> declaredAnnotations= (Map<Class<? extends Annotation>, Annotation>) AnnotationData_declaredAnotations.get(annotationData);

        Map<Class<? extends Annotation>, Annotation> newDeclaredAnnotations = new LinkedHashMap<>(annotations);
        newDeclaredAnnotations.put(annotationClass, annotation);
        Map<Class<? extends Annotation>, Annotation> newAnnotations ;
        if (declaredAnnotations == annotations) {
            newAnnotations = newDeclaredAnnotations;
        } else{
            newAnnotations = new LinkedHashMap<>(annotations);
            newAnnotations.put(annotationClass, annotation);
        }
        return AnnotationData_constructor.newInstance(newAnnotations, newDeclaredAnnotations, classRedefinedCount);
    }

    @SuppressWarnings("unchecked")
    public static <T extends Annotation> T annotationForMap(final Class<T> annotationClass, final Map<String, Object> valuesMap){
        return (T)AccessController.doPrivileged(new PrivilegedAction<Annotation>(){
            public Annotation run(){
                InvocationHandler handler;
                try {
                    handler = (InvocationHandler) AnnotationInvocationHandler_constructor.newInstance(annotationClass,new HashMap<>(valuesMap));
                } catch (InstantiationException | IllegalAccessException
                        | IllegalArgumentException | InvocationTargetException e) {
                    throw new IllegalStateException(e);
                }
                return (Annotation)Proxy.newProxyInstance(annotationClass.getClassLoader(), new Class[] { annotationClass }, handler);
            }
        });
    }
}

Приклад використання:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TestAnnotation {
    String value();
}

public static class TestClass{}

public static void main(String[] args) {
    TestAnnotation annotation = TestClass.class.getAnnotation(TestAnnotation.class);
    System.out.println("TestClass annotation before:" + annotation);

    Map<String, Object> valuesMap = new HashMap<>();
    valuesMap.put("value", "some String");
    RuntimeAnnotations.putAnnotation(TestClass.class, TestAnnotation.class, valuesMap);

    annotation = TestClass.class.getAnnotation(TestAnnotation.class);
    System.out.println("TestClass annotation after:" + annotation);
}

Вихід:

TestClass annotation before:null
TestClass annotation after:@RuntimeAnnotations$TestAnnotation(value=some String)

Обмеження цього підходу:

  • Нові версії Java можуть порушити код у будь-який час.
  • Наведений приклад працює лише для Java 8 - для того, щоб він працював для старих версій Java, потрібно було б перевірити версію Java під час виконання та змінити реалізацію відповідно.
  • Якщо анотований Клас буде перевизначений (наприклад, під час налагодження), анотація буде втрачена.
  • Не ретельно перевірено; не впевнений, чи є якісь погані побічні ефекти - використовуйте на свій страх і ризик ...

Хороша робота, я був би дуже вдячний, щоб вона працювала з Java 1.7, можливо, ця карта корисна: grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/…
gouessej

Будь-які пропозиції щодо того, як змусити це працювати для полів?
heez

3
@heez Я зробив цю роботу над полями та методами. Ви можете побачити AnnotationUtil.java .
Дін Сю

7

Можна створювати анотації під час виконання через проксі . Потім ви можете додати їх до своїх об’єктів Java за допомогою відображення, як пропонується в інших відповідях (але, мабуть, вам було б краще знайти альтернативний спосіб вирішити це, оскільки возиння з існуючими типами за допомогою відображення може бути небезпечним і важким для налагодження).

Але це не дуже просто ... Я написав бібліотеку під назвою, сподіваюсь, що Javanna просто зробити це легко, використовуючи чистий API.

Це в JCenter та Maven Central .

Використовуючи його:

@Retention( RetentionPolicy.RUNTIME )
@interface Simple {
    String value();
}

Simple simple = Javanna.createAnnotation( Simple.class, 
    new HashMap<String, Object>() {{
        put( "value", "the-simple-one" );
    }} );

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

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

Як бонус, ця бібліотека може також аналізувати класи анотацій і повертати значення анотації як карту:

Map<String, Object> values = Javanna.getAnnotationValues( annotation );

Це зручно для створення міні-фреймворків.

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