Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Modify field annotation value dynamically

Is is possible to change field annotation values at runtime?

I can access the values, but can't find a way to change them.

Access is possible with:

Article.class.declaredFields.find {it.name="annotatedField"}.declaredAnnotations
like image 618
Uros K Avatar asked Jun 23 '14 13:06

Uros K


People also ask

What is @target annotation in Java?

If an @Target meta-annotation is present, the compiler will enforce the usage restrictions indicated by ElementType enum constants, in line with JLS 9.7. 4. For example, this @Target meta-annotation indicates that the declared type is itself a meta-annotation type.

Can we extend annotation?

In Java SE 6, annotations cannot subclass one another, and an annotation is not allowed to extend/implement any interfaces.

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.


1 Answers

I think it would be best to keep a reference to an Annotation object in addition to your Field (or Object), and update the Annotation reference as you change its values. This way, when the implementation of annotations in Class.java changes, your code is less likely to break.

The answer linked in the question comments is useful for dealing with annotations containing a single element, but if you have multiple elements that you need to set, here is a more general solution that makes use of a proxy:

import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args) throws Exception {
        Foo foo = new Foo();
        Field field = foo.getClass().getDeclaredFields()[0];

        Anno anno = field.getAnnotation(Anno.class);
        System.out.println(String.format("Old properties: %s, %s, %s", anno.value(), anno.bar(), anno.barr()));

        Anno anno2 = (Anno) setAttrValue(anno, Anno.class, "value", "new");
        System.out.println(String.format("New properties: %s, %s, %s", anno2.value(), anno2.bar(), anno2.barr()));

        Anno anno3 = (Anno) setAttrValue(anno2, Anno.class, "bar", "new bar");
        System.out.println(String.format("New properties: %s, %s, %s", anno3.value(), anno3.bar(), anno3.barr())); 
    }

    public static Annotation setAttrValue(Annotation anno, Class<? extends Annotation> type, String attrName, Object newValue) throws Exception {
        InvocationHandler handler = new AnnotationInvocationHandler(anno, attrName, newValue);
        Annotation proxy = (Annotation) Proxy.newProxyInstance(anno.getClass().getClassLoader(), new Class[]{type}, handler);
        return proxy;
    }
}

class AnnotationInvocationHandler implements InvocationHandler {
    private Annotation orig;
    private String attrName;
    private Object newValue;

    public AnnotationInvocationHandler(Annotation orig, String attrName, Object newValue) throws Exception {
        this.orig = orig;
        this.attrName = attrName;
        this.newValue = newValue;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // "override" the return value for the property we want
        if (method.getName().equals(attrName) && args == null)
            return newValue;

        // keep other properties and methods we want like equals() and hashCode()
        else {
            Class<?>[] paramTypes = toClassArray(args);
            return orig.getClass().getMethod(method.getName(), paramTypes).invoke(orig, args);
        }
    }

    private static Class<?>[] toClassArray(Object[] arr) {
        if (arr == null)
            return null;
        Class<?>[] classArr = new Class[arr.length];
        for (int i=0; i<arr.length; i++)
            classArr[i] = arr[i].getClass();
        return classArr;
    }

}

class Foo {
    @Anno(value="old", bar="bar", barr="barr")
    public Object field1;
}

@Retention(RetentionPolicy.RUNTIME)
@interface Anno {
    String value();
    String bar();
    String barr();
}

Program output:

Old properties: old, bar, barr
New properties: new, bar, barr
New properties: new, new bar, barr
like image 64
nebulabrot Avatar answered Sep 23 '22 23:09

nebulabrot