it-swarm.com.ru

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

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

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

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

65
Clayton

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

21
Tom

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

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

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

38
ChssPly76

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

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

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

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

3
Renato