起初一看,我印象中是不行的,因为我一直以为XXX.class.getAnnotation(XXXAnnotation.class)
这个方法是一个native
方法
直到刚自己去看了一眼,哎呀,不是native
方法啊
好家伙,源来氏Class
类中有一个私有内部静态类AnnotationData
负责存储Class
的注解里的值(前提你的注解是RetentionPolicy.RUNTIME
的),当然存法也很简单就是用一个map
存储了注解类型和注解实例
所以看起来直接去修改对应Class
的AnnotationData
属性中的annotations map
里的注解实例就可以了
但是其实不然,因为我们反射是拿不到反射相关类的属性的,例如Class
,Field
这些,不然那可不乱了套了(相当于搁这搁这这种套娃操作了...),因此呢只能再换个思路
我们可以注意到,虽然注解实例缓存在Class
中,但是它确实可以通过getAnnotation
获取到对应的实例,而这个实例恰好是一个代理对象,其实也就是用咱们jdk
动态代理做的,只用debug
一看就很清楚了
既然是代理对象,那肯定有对应的InvocationHandler
啊,既然代理对象能够实现原注解的所有功能,那意味着其对应的InvocationHandler
肯定也包含了该注解的所有功能。
果不其然,注解对应InvocationHandler
是AnnotationInvocationHandler
,这个类就很憨厚了,其中的memberValues
属性就是我们需要的,怎么证实呢?
比如我们有个注解TestAnnotation
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TestAnnotation {
String value();
}
注解到一个叫Test
的类上,value
值就写成haha
我们直接通过getAnnotation
拿到对应的注解,再用Proxy.getInvocationHandler
拿到对应的InvocationHandler
TestAnnotation testAnnotation = Test.class.getDeclaredAnnotation(TestAnnotation.class);
InvocationHandler invocationHandler = Proxy.getInvocationHandler(testAnnotation);
这个debug
一看就很明显啦
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());
最终打印效果:
但是这样有个问题,什么呢?就是注解不仅仅是可以注解在类上的,也可以注解在字段上,方法上,所以我后面看了一下字段和方法上是怎么实现这个getAnnotation
,那肯定就不是用AnnotationData
啦,毕竟人家是Class
的内部类
Field
呢是内部有个私有declaredAnnotations
属性,是Map<Class<? extends Annotation>, Annotation>
的类型,那这个就和AnnotationData
里的annotations
类似啦
而Method
是其父类Executable
有个declaredAnnotations
属性,还是Map<Class<? extends Annotation>, Annotation>
类型
那说明无论是怎么获取注解,都是从某个地方的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