Agregar anotaciones Java en tiempo de ejecución

¿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óduloA y móduloB. el módulo B depende del módulo A, que no depende de nada. (modA es mi core datatypes e interfaces y tal, modB es db / data layer) modB también depende de externalLibrary. En mi caso, modB está entregando una clase de modA a externalLibrary, que necesita ciertos métodos para ser anotados. Las anotaciones específicas son todas parte de externalLib y, como dije, modA no depende de externalLib y me gustaría mantenerlo de esa manera.

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

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

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

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

Sin embargo, esto es cualquier cosa menos simple y directo. NO recomendaría este enfoque y te sugiero que consideres la respuesta de Tom a menos que necesites hacer esto para un gran número de clases (o dichas clases no están disponibles para ti hasta el tiempo de ejecución y así escribir un adaptador es imposible).

También es posible agregar una Anotación a una clase 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 raro y puede romperse en cualquier momento para las versiones de Java más nuevas. Pero para pruebas / prototipos rápidos y sucios este enfoque puede ser útil a veces.

Ejemplo de proove 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  void putAnnotation(Class c, Class annotationClass, Map valuesMap){ putAnnotation(c, annotationClass, annotationForMap(annotationClass, valuesMap)); } public static  void putAnnotation(Class c, Class 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  Object /*AnnotationData*/ createAnnotationData(Class c, Object /*AnnotationData*/ annotationData, Class annotationClass, T annotation, int classRedefinedCount) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { Map, Annotation> annotations = (Map, Annotation>) AnnotationData_annotations.get(annotationData); Map, Annotation> declaredAnnotations= (Map, Annotation>) AnnotationData_declaredAnotations.get(annotationData); Map, Annotation> newDeclaredAnnotations = new LinkedHashMap<>(annotations); newDeclaredAnnotations.put(annotationClass, annotation); Map, 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 annotationForMap(final Class annotationClass, final Map valuesMap){ return (T)AccessController.doPrivileged(new PrivilegedAction(){ 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 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, lo que hace que funcione para las 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 se redefine (por ejemplo, durante la depuración), la anotación se perderá.
  • No completamente probado; no estoy seguro de si hay algún efecto secundario negativo: utilícelo bajo su propio riesgo

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

Pero no es muy fácil … Escribí una biblioteca llamada, espero apropiadamente, 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() {{ put( "value", "the-simple-one" ); }} ); 

Si alguna entrada del mapa no coincide con los campos declarados y el tipo (s) de anotación, se lanza una excepción. Si falta algún valor que no tenga valor predeterminado, se lanza 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 comstackción.

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

 Map values = Javanna.getAnnotationValues( annotation ); 

Esto es conveniente para crear mini-frameworks.