[Java] Dynamically modify an attribute value of an annotation through reflection

Original: https://segmentfault.com/a/1190000011213222

I saw a question last night , to the effect that the landlord hopes to dynamically create multiple Spring scheduled tasks.

I am not very familiar with this topic, but according to the description of the topic and  the information about creating timed tasks in Spring  , I found that this may involve dynamically modifying the attribute values ​​of annotations through Java code.

I tried this today and found that it is possible to dynamically modify the attribute value of the annotation through reflection:

As we all know, java/lang/reflect under this package are Java's reflection classes and tools.

Annotation Annotations are also located in this package. Since the introduction of Java 5.0, annotations have become a very important part of the Java platform, such as  @Override@Deprecated.

There is already a lot of information on the Internet for more detailed information and usage of annotations, so I won't repeat them here.

An annotation  builds on  this situation by @Retention specifying its lifetime, which is discussed in this article by dynamically modifying annotation attribute values  . @Retention(RetentionPolicy.RUNTIM)After all, this kind of annotation can be operated by reflection mechanism at runtime.

So now we define an  @Foo annotation that has a property of type  String ,  which is value applied Fieldon top of:


/**
 * Created by krun on 2017/9/18.
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Foo {
    String value();
}

Then define a normal Java object  Bar, it has a private Stringproperty  , and set  the   annotation of the valproperty value for it :"fff"@Foo


public class Bar {

    @Foo ("fff")
    private String val;
}

Next in the  main method we try to modify  the attribute value Bar.val of the  @Fooannotation  "ddd".

First, get the annotation attribute value normally:


/**
 * Created by krun on 2017/9/18.
 */
public class Main {
    public static void main(String ...args) throws NoSuchFieldException {
        //获取Bar实例
        Bar bar = new Bar();
        //获取Bar的val字段
        Field field = Bar.class.getDeclaredField("val");
        //获取val字段上的Foo注解实例
        Foo foo = field.getAnnotation(Foo.class);
        //获取Foo注解实例的 value 属性值
        String value = foo.value();
        //打印该值
        System.out.println(value); // fff
    }
}

First, we need to know where the value of the annotation exists.

At  String value = foo.value(); the breakpoint, we can find it by running:

There are so many variables in the current stack, but one of them is very special: foo, is actually an Proxyinstance.

ProxyAlso  java/lang/reflectthe next thing, its role is to generate a proxy for a Java class, like this:


public interface A {
    String func1();
}

public class B implements A {
    
    @Override
    public String func1() { //do something ... }
    
    public String func2() { //do something ... };
}

public static void main(String ...args) {
    B bInstance = new B();
    
    B bProxy = Proxy.newProxyInstance(
        B.class.getClassLoader(),    // B 类的类加载器
        B.class.getInterfaces(), // B 类所实现的接口,如果你想拦截B类的某个方法,必须让这个方法在某个接口中声明并让B类实现该接口
        new InvocationHandler() { // 调用处理器,任何对 B类所实现的接口方法的调用都会触发此处理器
            @Override
            public Object invoke (Object proxy, // 这个是代理的实例,method.invoke时不能使用这个,否则会死循环
                                  Method method, // 触发的接口方法
                                  Object[] args // 此次调用该方法的参数
                                  ) throws Throwable {
                System.out.println(String.format("调用 %s 之前", method.getName()));
                /**
                 * 这里必须使用B类的某个具体实现类的实例,因为触发时这里的method只是一个接口方法的引用,
                 * 也就是说它是空的,你需要为它指定具有逻辑的上下文(bInstance)。
                 */
                Object obj = method.invoke(bInstance, args); 
                System.out.println(String.format("调用 %s 之后", method.getName()));
                return obj; //返回调用结果
            }
        }
    );
}

This way you can intercept a method call of this Java class, but you can only intercept  func1the call, think why?

Then pay attention:

ClassLoader This classwill be there, and annotations are no exception. So what does annotation interfaceshave to do with it?

An annotation is essentially an interface, and its essence is defined as:  interface SomeAnnotation extends Annotation.
This  Annotation interface is located in  java/lang/annotation the package, and the first sentence in its comment is The common interface extended by all annotation types.

In this way, the Foo annotation itself is just an interface, which means that it does not have any code logic, so where do its  value properties exist?

Expand  foo to find:

This  Proxy instance holds one  AnnotationInvocationHandler, remember how to create an instance earlier  Proxy ? The third parameter is one  InvocationHandler.
Looking at the name handleris Annotationunique, let's take a look at its code:


class AnnotationInvocationHandler implements InvocationHandler, Serializable {

    private final Class<? extends Annotation> type;
    private final Map<String, Object> memberValues;
    private transient volatile Method[] memberMethods = null;
    
    /* 后续无关代码就省略了,想看的话可以查看 sun/reflect/annotation/AnnotationInvocationHandler */
   
}

我们一眼就可以看到一个有意思的名字: memberValues,这是一个Map,而断点中可以看到这是一个 LinknedHashMapkey为注解的属性名称,value即为注解的属性值。

现在我们找到了注解的属性值存在哪里了,那么接下来的事就好办了:

/**
 * Created by krun on 2017/9/18.
 */
public class Main {
    public static void main(String ...args) throws NoSuchFieldException, IllegalAccessException {
        //获取Bar实例
        Bar bar = new Bar();
        //获取Bar的val字段
        Field field = Bar.class.getDeclaredField("val");
        //获取val字段上的Foo注解实例
        Foo foo = field.getAnnotation(Foo.class);
        //获取 foo 这个代理实例所持有的 InvocationHandler
        InvocationHandler h = Proxy.getInvocationHandler(foo);
        // 获取 AnnotationInvocationHandler 的 memberValues 字段
        Field hField = h.getClass().getDeclaredField("memberValues");
        // 因为这个字段事 private final 修饰,所以要打开权限
        hField.setAccessible(true);
        // 获取 memberValues
        Map memberValues = (Map) hField.get(h);
        // 修改 value 属性值
        memberValues.put("value", "ddd");
        // 获取 foo 的 value 属性值
        String value = foo.value();
        System.out.println(value); // ddd
    }


Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324515592&siteId=291194637
Recommended