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

StackOverflow https://stackoverflow.com/questions/1635108

  •  06-07-2019
  •  | 
  •  

Вопрос

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

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

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

Это было полезно?

Решение

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

Другие советы

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

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

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

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

Можно создавать аннотации во время выполнения с помощью 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 );

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top