Добавление аннотаций Java во время выполнения - PullRequest
66 голосов
/ 28 октября 2009

Можно ли добавить аннотацию к объекту (в моем случае, в частности, к методу) во время выполнения?

Для более подробного объяснения: у меня есть два модуля, модуль A и модуль B. moduleB зависит от moduleA, который не зависит ни от чего. (modA - это мои основные типы данных и интерфейсы и так далее, modB - это дБ / уровень данных) modB также зависит от externalLibrary. В моем случае, modB передает класс из modA в externalLibrary, для которого необходимо добавить определенные методы. Все конкретные аннотации являются частью externalLib, и, как я уже сказал, modA не зависит от externalLib, и я бы хотел оставить его таким.

Итак, возможно ли это, или у вас есть предложения по другим способам решения этой проблемы?

Ответы [ 4 ]

39 голосов
/ 28 октября 2009

Это возможно через инструментальную библиотеку байт-кода, такую ​​как Javassist .

В частности, обратите внимание на класс AnnotationsAttribute для примера создания и установки аннотаций и раздел учебника по API байт-кода для общих рекомендаций по работе с файлами классов.

Это совсем не просто и понятно, хотя - я бы НЕ рекомендовал этот подход и предлагал бы вместо этого рассмотреть ответ Тома, если вам не нужно делать это для огромного числа классов (или если указанные классы недоступны до времени выполнения и поэтому написание адаптера невозможно).

21 голосов
/ 28 октября 2009

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

20 голосов
/ 17 мая 2015

Также возможно добавить аннотацию к классу 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 во время выполнения и соответственно изменить реализацию.
  • Если аннотированный класс получает переопределено (например, во время отладки), аннотация будет потеряна.
  • Тщательно не проверено; не уверен, есть ли какие-либо плохие побочные эффекты - используйте на свой страх и риск ...
4 голосов
/ 25 ноября 2016

Можно создавать аннотации во время выполнения через Proxy . Затем вы можете добавить их к своим объектам Java с помощью отражения, как предлагалось в других ответах (но вам, вероятно, было бы лучше найти альтернативный способ справиться с этим, поскольку путаница с существующими типами с помощью отражения может быть опасной и сложной для отладки). 1003 *

Но это не очень легко ... Я написал библиотеку, которая, я надеюсь, уместна, 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 );

Это удобно для создания мини-фреймворков.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...