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 Field
on 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 String
property , and set the annotation of the val
property 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 @Foo
annotation "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 Proxy
instance.
Proxy
Also java/lang/reflect
the 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 func1
the call, think why?
Then pay attention:
ClassLoader
This class
will be there, and annotations are no exception. So what does annotation interfaces
have 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 handler
is Annotation
unique, 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,而断点中可以看到这是一个 LinknedHashMap
,key
为注解的属性名称,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
}