起初一看,我印象中是不行的,因为我一直以为XXX.class.getAnnotation(XXXAnnotation.class)
这个方法是一个native
方法
直到刚自己去看了一眼,哎呀,不是native
方法啊
![image.png image.png](/img/bVcN3y2)
好家伙,源来氏Class
类中有一个私有内部静态类AnnotationData
负责存储Class
的注解里的值(前提你的注解是RetentionPolicy.RUNTIME
的),当然存法也很简单就是用一个map
存储了注解类型和注解实例
![image.png image.png](/img/bVcN33v)
![image.png image.png](/img/bVcN3HB)
所以看起来直接去修改对应Class
的AnnotationData
属性中的annotations map
里的注解实例就可以了
但是其实不然,因为我们反射是拿不到反射相关类的属性的,例如Class
,Field
这些,不然那可不乱了套了(相当于搁这搁这这种套娃操作了...),因此呢只能再换个思路
我们可以注意到,虽然注解实例缓存在Class
中,但是它确实可以通过getAnnotation
获取到对应的实例,而这个实例恰好是一个代理对象,其实也就是用咱们jdk
动态代理做的,只用debug
一看就很清楚了
![image.png image.png](/img/bVcN4fr)
既然是代理对象,那肯定有对应的InvocationHandler
啊,既然代理对象能够实现原注解的所有功能,那意味着其对应的InvocationHandler
肯定也包含了该注解的所有功能。
果不其然,注解对应InvocationHandler
是AnnotationInvocationHandler
,这个类就很憨厚了,其中的memberValues
属性就是我们需要的,怎么证实呢?
![image.png image.png](/img/bVcN4cL)
比如我们有个注解TestAnnotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TestAnnotation {
String value();
}
注解到一个叫Test
的类上,value
值就写成haha
![image.png image.png](/img/bVcN4fR)
我们直接通过getAnnotation
拿到对应的注解,再用Proxy.getInvocationHandler
拿到对应的InvocationHandler
TestAnnotation testAnnotation = Test.class.getDeclaredAnnotation(TestAnnotation.class);
InvocationHandler invocationHandler = Proxy.getInvocationHandler(testAnnotation);
这个debug
一看就很明显啦
![image.png image.png](/img/bVcN4gR)
memberValues
中就藏着我们的目标,所以我们可以直接通过反射修改它就可以了,比如这样
TestAnnotation annotation = Test.class.getAnnotation(TestAnnotation.class);
System.out.println("before value: " + annotation.value());
InvocationHandler invocationHandler = Proxy.getInvocationHandler(annotation);
Field value = invocationHandler.getClass().getDeclaredField("memberValues");
value.setAccessible(true);
Map<String, Object> memberValues = (Map<String, Object>) value.get(invocationHandler);
memberValues.put("value", "i am new value");
annotation = Test.class.getAnnotation(TestAnnotation.class);
System.out.println("after value: " + annotation.value());
最终打印效果:
![image.png image.png](/img/bVcN4jh)
但是这样有个问题,什么呢?就是注解不仅仅是可以注解在类上的,也可以注解在字段上,方法上,所以我后面看了一下字段和方法上是怎么实现这个getAnnotation
,那肯定就不是用AnnotationData
啦,毕竟人家是Class
的内部类
Field
呢是内部有个私有declaredAnnotations
属性,是Map<Class<? extends Annotation>, Annotation>
的类型,那这个就和AnnotationData
里的annotations
类似啦
![image.png image.png](/img/bVcN4kb)
而Method
是其父类Executable
有个declaredAnnotations
属性,还是Map<Class<? extends Annotation>, Annotation>
类型
![image.png image.png](/img/bVcN4kW)
那说明无论是怎么获取注解,都是从某个地方的Map<Class<? extends Annotation>, Annotation>
缓存中获取。那怎么把不同地方,例如类,方法,字段聚合在一个工具类中呢?
铛铛铛!恰好有个顶层的接口把它们聚在了一起AnnotatedElement
。因此工具方法应运而生(其实他们的getAnnotation
方法就是来自AnnotatedElement
接口的方法)
public static void modify(AnnotatedElement element,
Class<? extends Annotation> annotationClass,
String key, Object value) throws NoSuchFieldException, IllegalAccessException {
Annotation annotationToBeModified = element.getAnnotation(annotationClass);
if (annotationToBeModified == null) return;
InvocationHandler invocationHandler = Proxy.getInvocationHandler(annotationToBeModified);
Field memberValuesField = invocationHandler.getClass().getDeclaredField("memberValues");
memberValuesField.setAccessible(true);
Map<String, Object> memberValues = (Map<String, Object>) memberValuesField.get(invocationHandler);
memberValues.put(key, value);
}
参数也很好理解,AnnotatedElement
就是被注解的地方啦,如果呢你是注解的类,就传Class
,注解的方法,就传某个Method
,注解的是字段,就传Field
,就算注解的参数,那就传某个Parameter
,总之理论上啥都够啦
最后题主提到的MySQL
那就不是大问题啦,毕竟它只是一个数据库而已,如果真有某个工具可以实现题主的需求,那提供这个工具的人也太嘞了。。。竟然必须要MySQL
,所以啊,这个问题最大的点应该还是如何实现运行时修改注解里的值,不过最后实现下来看起来也没多少代码,只是一个小工具,可以放到自己项目中进行一些组装成为其他业务组件的一个部分吧。
那就酱。。。o( ̄▽ ̄)d