I want to dynamically create multiple Spring scheduled tasks.

I’m not familiar with this topic, but based on the description and reading about Spring creating scheduled tasks, I found that this might involve dynamically modifying the attribute value of an annotation through Java code.

We tried this out today and found that it is possible to dynamically modify the attribute value of an annotation by reflection:

As we all know, Java /lang/ Reflect is full of Java reflection classes and tools.

The Annotation Annotation is also in this package. Annotations have been an important part of the Java platform since they were introduced in Java version 5.0, with common ones such as @Override and @deprecated.

More detailed information about annotations and how to use them is available on the web and will not be covered here.

An annotation specifies its lifetime through @Retention. The dynamic modification of the annotation attribute values discussed in this article is based on the @Retention(retentionPolicy.runtim) case. After all, such annotations can only be operated on at runtime via reflection.

So now we define an @foo annotation that has a value attribute of type String and applies it to Field:




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

Define a plain Java object Bar with a private String attribute val and set @foo to it with the attribute value “FFF” :




public class Bar {

    @Foo ("fff")
    private String val;
}
Copy the code

Next in the main method we try to change the @foo attribute on bar. val to “DDD”.

First, get the value of the annotation property as normal:




/** * Created by krun on 2017/9/18. */
public class Main {
    public static void main(String ... args) throws NoSuchFieldException {
        // Get the Bar instance
        Bar bar = new Bar();
        // Get the val field of Bar
        Field field = Bar.class.getDeclaredField("val");
        // Get the Foo annotation instance on the val field
        Foo foo = field.getAnnotation(Foo.class);
        // Get the value attribute of the Foo annotation instance
        String value = foo.value();
        // Prints the value
        System.out.println(value); // fff}}Copy the code

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

In String value = foo.value(); At the break point, we can run and find:

There are several variables in the current stack, but one of them is unusual: Foo is actually a Proxy instance.

Proxy is also under Java /lang/ Reflect, which generates a Proxy for a Java class, like this:




public interface A {
    String func1(a);
}

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

public static void main(String ... args) {
    B bInstance = new B();
    
    B bProxy = Proxy.newProxyInstance(
        B.class.getClassLoader(),    // Class loader of class B
        B.class.getInterfaces(), If you want to intercept a method of class B, you must have that method declared in an interface and class B implement that interface
        new InvocationHandler() { // Calls the handler, which is triggered by any call to the interface methods implemented by class B
            @Override
            public Object invoke (Object proxy, // this is an instance of a proxy, method.invoke cannot use this method, otherwise an infinite loop will be generated method method, // trigger the interface method Object[] args // call this method parameter) throws Throwable {
                System.out.println(String.format("Before calling %s", method.getName()));
                /** * An instance of an implementation class of class B must be used here, because method is only a reference to an interface method when fired, * that is, it is empty and you need to specify a logical context for it (bInstance). * /
                Object obj = method.invoke(bInstance, args); 
                System.out.println(String.format("After calling %s", method.getName()));
                return obj; // returns the result of the call}}); }Copy the code

So you can intercept a method call of this Java class, but you can only intercept a call of func1. Why?

So pay attention:

ClassLoader this is a class, annotations are no exception. So what do annotations have to do with interfaces?

An Annotation is essentially an interface, defined as interface SomeAnnotation extends Annotation. The Annotation interface is located in The Java/Lang/Annotation package and The first sentence in The Annotation is The Common Interface Extended by all Annotation Types.

So the Foo annotation itself is just an interface, which means it doesn’t have any code logic, so where does its value attribute live?

Expand foo to find:

The Proxy instance holds a AnnotationInvocationHandler, remember before mentioned how to create a Proxy instance? The third parameter is an InvocationHandler. Look at the name. This handler is unique to annotations, so let’s 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;
    
    Has nothing to do / * subsequent code is omitted, want to see can see sun/AnnotationInvocationHandler * / / reflect an annotation
   
}
Copy the code

We can immediately see an interesting name: memberValues, which is a Map, and in the breakpoint we can see that this is a LinknedHashMap, where key is the attribute name of the annotation and value is the attribute value of the annotation.

Now that we know where the attribute value of the annotation exists, we can do the following:



/** * Created by krun on 2017/9/18. */
public class Main {
    public static void main(String ... args) throws NoSuchFieldException, IllegalAccessException {
        // Get the Bar instance
        Bar bar = new Bar();
        // Get the val field of Bar
        Field field = Bar.class.getDeclaredField("val");
        // Get the Foo annotation instance on the val field
        Foo foo = field.getAnnotation(Foo.class);
        // Get the InvocationHandler held by the proxy instance foo
        InvocationHandler h = Proxy.getInvocationHandler(foo);
        / / get AnnotationInvocationHandler memberValues fields
        Field hField = h.getClass().getDeclaredField("memberValues");
        // Because this field is private final, you need to turn on permissions
        hField.setAccessible(true);
        / / get memberValues
        Map memberValues = (Map) hField.get(h);
        // Change the value attribute value
        memberValues.put("value"."ddd");
        // Get the value of the foo property
        String value = foo.value();
        System.out.println(value); // ddd}}Copy the code