Pregunta

¿Es posible agregar una anotación a un objeto (en mi caso en particular, un Método) en tiempo de ejecución?

Para una explicación un poco más: tengo dos módulos, módulo A y módulo B. El módulo B depende del módulo A, que no depende de nada. (modA es mi tipo de datos e interfaces principales y tal, modB es db / capa de datos) modB también depende de externalLibrary. En mi caso, modB está entregando una clase de modA a externalLibrary, que necesita que se anoten ciertos métodos. Las anotaciones específicas son parte de externalLib y, como dije, modA no depende de externalLib y me gustaría mantenerlo así.

Entonces, ¿es esto posible, o tiene sugerencias para otras formas de ver este problema?

¿Fue útil?

Solución

No es posible agregar una anotación en tiempo de ejecución, parece que necesita introducir un adaptador que el módulo B usa para envolver el objeto del módulo A exponiendo los métodos anotados requeridos.

Otros consejos

Es posible a través de la biblioteca de instrumentación de bytecode como Javassist .

En particular, eche un vistazo a AnnotationsAttribute para obtener un ejemplo sobre cómo crear / establecer anotaciones y sección tutorial sobre API de bytecode para obtener pautas generales sobre cómo manipular archivos de clase.

Sin embargo, esto es cualquier cosa menos simple y directo: NO recomendaría este enfoque y sugeriría que considere la respuesta de Tom en su lugar a menos que necesite hacer esto para una gran cantidad de clases (o dichas clases no están disponibles para usted hasta el tiempo de ejecución y así escribir un adaptador es imposible).

También es posible agregar una anotación a una clase de Java en tiempo de ejecución utilizando la API de reflexión de Java. Esencialmente uno debe recrear los mapas de Anotación internos definidos en la clase java.lang.Class (o para Java 8 definido en la clase interna java.lang.Class.AnnotationData ). Naturalmente, este enfoque es bastante hacky y podría romperse en cualquier momento para las nuevas versiones de Java. Pero para realizar pruebas / prototipos rápidos y sucios, este enfoque puede ser útil a veces.

Ejemplo de prueba de concepto para 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);
            }
        });
    }
}

Ejemplo de uso:

@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);
}

Salida:

TestClass annotation before:null
TestClass annotation after:@RuntimeAnnotations$TestAnnotation(value=some String)

Limitaciones de este enfoque:

  • Las nuevas versiones de Java pueden romper el código en cualquier momento.
  • El ejemplo anterior solo funciona para Java 8: hacer que funcione para versiones anteriores de Java requeriría verificar la versión de Java en tiempo de ejecución y cambiar la implementación en consecuencia.
  • Si la clase anotada obtiene redefinida (por ejemplo, durante la depuración) , la anotación se perderá.
  • No probado a fondo; no estoy seguro si hay efectos secundarios negativos: use bajo su propio riesgo ...

Es posible crear anotaciones en tiempo de ejecución a través de un Proxy . Luego puede agregarlos a sus objetos Java mediante la reflexión como se sugiere en otras respuestas (pero probablemente sería mejor encontrar una forma alternativa de manejar eso, ya que jugar con los tipos existentes a través de la reflexión puede ser peligroso y difícil de depurar).

Pero no es muy fácil ... Escribí una biblioteca llamada, espero que sea apropiada, Javanna solo para hacer esto fácilmente usando una API limpia.

Está en JCenter y Maven Central .

Utilizándolo:

@Retention( RetentionPolicy.RUNTIME )
@interface Simple {
    String value();
}

Simple simple = Javanna.createAnnotation( Simple.class, 
    new HashMap<String, Object>() {{
        put( "value", "the-simple-one" );
    }} );

Si alguna entrada del mapa no coincide con los campos y tipos declarados de anotación, se emite una Excepción. Si falta algún valor que no tenga un valor predeterminado, se genera una excepción.

Esto hace posible suponer que cada instancia de anotación que se crea con éxito es tan segura de usar como una instancia de anotación en tiempo de compilación.

Como beneficio adicional, esta biblioteca también puede analizar las clases de anotación y devolver los valores de la anotación como un Mapa:

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

Esto es conveniente para crear mini-frameworks.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top