Добавление аннотаций Java во время выполнения

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

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

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

Ответ 1

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

Ответ 2

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

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

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

Ответ 3

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

Ответ 4

Можно создавать аннотации во время выполнения через Proxy. Затем вы можете добавить их к вашим объектам 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 );

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