Java 动态修改注解的属性值

前言

以往来看,注解的属性值一般都是“硬编码”。但最近在开发过程中遇到了需要根据运行环境来设置 Retention 为 RUNTIME (运行期保留) 的注解属性值的需求。举个例子:

@Table(name="t1")
public class Test {
  private String id;
 ...
}

对于上述类 Test,其上有一个注解 @Table,需求是这样的: 在测试环境 name 值为"t1",在其他环境 name 值为"t2"

实现方法

知识点:

  • 保留策略为 RUNTIME 的注解在运行期是保留的。
  • 出于某些技术原因,Java 虚拟机使用的“真实”注释类的实例是动态代理的实例。
  • Java 注解有一个名为 memberValues 的私有Map,其中存储了属性名称和属性值的k-v对。

基于上述知识点,可以通过反射来访问实例,然后用给定的新值替换现有值。

相关的类:

  • Proxy JDK 动态代理大佬
    可通过其 getInvocationHandler 方法获取注解的代理实例
  • InvocationHandler 调用处理器,每一个被代理的实例都有一个调用处理器
    通过反射获取被代理类的实例的属性值

示例代码:

// 根据运行环境获取表名
String tableName = getTable();
// 获取 Test 上的注解
Table annoTable = Test.class.getAnnotation(Table.class);

if (annoTable == null) {
  throw new RuntimeException("please add @Table for Test");
}
// 获取代理处理器
InvocationHandler invocationHandler = Proxy.getInvocationHandler(annoTable);
// 过去私有 memberValues 属性
Field f = invocationHandler.getClass().getDeclaredField("memberValues");
f.setAccessible(true);
// 获取实例的属性map
Map<String, Object> memberValues = (Map<String, Object>) f.get(invocationHandler);
// 修改属性值
memberValues.put("name", tableName);

推荐阅读更多精彩内容