Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding Java Annotations at Runtime

Is it possible to add an annotation to an object (in my case in particular, a Method) at runtime?

For a bit more explanation: I have two modules, moduleA and moduleB. moduleB depends upon moduleA, which doesn't depend upon anything. (modA is my core datatypes and interfaces and such, modB is db/data layer) modB also depends on externalLibrary. In my case, modB is handing off a class from modA to externalLibrary, which needs certain methods to be annotated. The specific annotations are all part of externalLib and, as I said, modA doesn't depend on externalLib and I'd like to keep it that way.

So, is this possible, or do you have suggestions for other ways of looking at this problem?

like image 999
Clayton Avatar asked Oct 28 '09 03:10

Clayton


People also ask

Can you apply more than one annotation on any method in Java?

It is also possible to use multiple annotations on the same declaration: @Author(name = "Jane Doe") @EBook class MyClass { ... } If the annotations have the same type, then this is called a repeating annotation: @Author(name = "Jane Doe") @Author(name = "John Smith") class MyClass { ... }

How do you inherit annotations?

Annotations, just like methods or fields, can be inherited between class hierarchies. If an annotation declaration is marked with @Inherited , then a class that extends another class with this annotation can inherit it. The annotation can be overridden in case the child class has the annotation.


2 Answers

It's possible via bytecode instrumentation library such as Javassist.

In particular, take a look at AnnotationsAttribute class for an example on how to create / set annotations and tutorial section on bytecode API for general guidelines on how to manipulate class files.

This is anything but simple and straightforward, though - I would NOT recommend this approach and suggest you consider Tom's answer instead unless you need to do this for a huge number of classes (or said classes aren't available to you until runtime and thus writing an adapter is impossible).

like image 160
ChssPly76 Avatar answered Oct 21 '22 05:10

ChssPly76


It is also possible to add an Annotation to a Java class at runtime using the Java reflection API. Essentially one must recreate the internal Annotation maps defined in the class java.lang.Class (or for Java 8 defined in the internal class java.lang.Class.AnnotationData). Naturally this approach is quite hacky and might break at any time for newer Java versions. But for quick and dirty testing/prototyping this approach can be useful at times.

Proove of concept example for 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);             }         });     } } 

Usage example:

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

Output:

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

Limitations of this approach:

  • New versions of Java may break the code at any time.
  • The above example only works for Java 8 - making it work for older Java versions would require checking the Java version at runtime and changing the implementation accordingly.
  • If the annotated Class gets redefined (e.g. during debugging), the annotation will be lost.
  • Not thoroughly tested; not sure if there are any bad side effects - use at your own risk...
like image 43
Balder Avatar answered Oct 21 '22 06:10

Balder