Adding Java Annotations at Runtime
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).
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:nullTestClass 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...
It's not possible to add an annotation at runtime, it sounds like you need to introduce an adapter that module B uses to wrap the object from module A exposing the required annotated methods.