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


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

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

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

4 62

4 ответа:

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

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

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

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

также можно добавить аннотацию к классу Java во время выполнения с помощью API отражения Java. По существу, необходимо воссоздать внутренние карты аннотаций, определенные в классе java.lang.Class (или для Java 8, определенного во внутреннем классе java.lang.Class.AnnotationData). Естественно, этот подход довольно хаки и может сломаться в любое время для более новых версий Java. Но для быстрого и грязного тестирования / прототипирования этот подход может быть полезен в разы.

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

но это не очень легко... Я написал библиотеку под названием, надеюсь, соответствующим образом,Javanna просто сделать это легко с помощью чистой ПРИКЛАДНОЙ ПРОГРАММНЫЙ ИНТЕРФЕЙС.

Это 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 );

это удобно для создания мини-рамок.