反射动态修改注解属性本质是操作AnnotationInvocationHandler的memberValues映射,仅适用于@Retention(RetentionPolicy.RUNTIME)注解,可实现多环境差异化配置,但仅对当前JVM有效且不能新增属性。
反射动态修改注解属性,本质是绕过注解的“只读”表象,直接操作其底层代理对象的
映射。它不改变源码,却能在运行时按需调整行为——特别适合开发、测试、预发等多环境共用一套代码,但需差异化配置的场景(比如只在 dev 环境开启懒加载、关闭超时重试、启用调试日志开关等)。
前提:注解必须声明为 RUNTIME 生命周期
只有带
的注解,才能在 JVM 运行期被反射读取和操作。编译期(SOURCE)或类加载期(CLASS)的注解无法触达。
例如,一个用于远程服务引用的自定义注解:
核心原理:穿透 JDK 动态代理的 memberValues
Java 中所有运行时注解实例,实际都是由
代理的。它的私有字段
(类型为
)真正存储着你声明的属性键值对。
所以修改注解属性 = 获取该代理对象 → 反射访问并修改其
。
关键步骤如下:
通过
获取注解实例
调用
拿到代理处理器
用反射获取处理器内部的
字段,并设为可访问
向该
中
新值(如
)
典型应用:按 profile 自动注入开发专用配置
假设项目使用 Spring Boot,希望在
时,统一将所有
注解的
、
,而生产环境保持默认。
可在 Spring 容器刷新前(如实现
)执行批量修正:
这样无需改任何业务类上的注解,也无需写大量条件判断,就能实现环境感知的配置注入。
注意事项与限制
修改仅对当前 JVM 实例有效,不影响字节码文件
不能新增属性,只能覆盖已有 key;key 名必须与注解接口中方法名完全一致(区分大小写)
若注解属性是枚举、数组或嵌套注解,需确保 put 的值类型匹配,否则运行时报
Spring 的
、
等原生注解不可直接修改——它们不是靠反射读取注解属性驱动逻辑,而是由 Spring 自己解析并缓存结果;需在 Spring 解析前干预(如扩展
或自定义
)
memberValues@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) // ✅ 必须有这一行
public @interface RemoteService {
String group() default "";
int timeout() default 3000;
boolean lazy() default false;
}AnnotationInvocationHandlermemberValuesMapmemberValuesfield.getAnnotation(YourAnnotation.class)Proxy.getInvocationHandler(annotation)memberValuesMapputput("lazy", true)spring.profiles.active=dev@RemoteServicelazy=truetimeout=1000BeanFactoryPostProcessorpublic class DevAnnotationConfigurer implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
if (!"dev".equals(System.getProperty("spring.profiles.active"))) return;
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition bd = beanFactory.getBeanDefinition(beanName);
Class> clazz = Class.forName(bd.getBeanClassName());
for (Field field : clazz.getDeclaredFields()) {
RemoteService anno = field.getAnnotation(RemoteService.class);
if (anno != null) {
InvocationHandler handler = Proxy.getInvocationHandler(anno);
Field mValues = handler.getClass().getDeclaredField("memberValues");
mValues.setAccessible(true);
Map map = (Map) mValues.get(handler);
map.put("lazy", true);
map.put("timeout", 1000);
}
}
}
}
} ClassCastException@Value@ScheduledAnnotationConfigUtilsBeanPostProcessor