淺談運(yùn)行時(shí)修改Java注解的值
這里介紹如何在運(yùn)行時(shí)修改注解的值
基本原理
查看JDK中Annotation接口的注釋,說(shuō)明所有注解都擴(kuò)展自Annotation接口。換言之,注解本質(zhì)上就是一個(gè)繼承了Annotation的接口
package java.lang.annotation;
/**
* The common interface extended by all annotation types.
* ...
* @author Josh Bloch
* @since 1.5
*/
public interface Annotation {
...
}
這里,我們先自定義一個(gè)注解@MyLog,同時(shí)定義一個(gè)類UserInfoService來(lái)使用該注解
/**
* 自定義注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
String level() default "TRACE";
}
...
public class UserInfoService {
@MyLog( level = "DEBUG")
public void attachById(Integer userId) {
}
@MyLog( level = "INFO")
public void batchDelete(List<Integer> ids) {
}
}
現(xiàn)在我們通過(guò)debug的方式來(lái)查看反射后的注解對(duì)象。由于注解的具體實(shí)現(xiàn)類是利用JDK動(dòng)態(tài)代理生成的。故我們通過(guò)反射獲得的注解對(duì)象,實(shí)際上是運(yùn)行時(shí)生成的動(dòng)態(tài)代理對(duì)象Proxy
public class RuntimeModifyAnnoTest {
public static void main(String[] args) throws NoSuchMethodException {
test1();
}
public static void test1() throws NoSuchMethodException {
// 獲取類的方法
Method attachByIdMethod = UserInfoService.class.getDeclaredMethod("attachById", Integer.class);
// 獲取方法的注解
MyLog attachByIdMyLog = attachByIdMethod.getDeclaredAnnotation( MyLog.class );
String level = attachByIdMyLog.level();
System.out.println("Level : " + level);
}
}
而當(dāng)我們通過(guò)這個(gè)動(dòng)態(tài)代理對(duì)象Proxy訪問(wèn)level屬性值時(shí),其會(huì)通過(guò)調(diào)用 AnnotationInvocationHandler 的invoke方法來(lái)實(shí)現(xiàn)。進(jìn)一步地觀察invoke方法的源碼,不難看出其最終是從memberValues這個(gè)Map中獲取到對(duì)應(yīng)的值
實(shí)踐
現(xiàn)在,我們?nèi)绻谕谶\(yùn)行時(shí)修改注解的屬性值就非常簡(jiǎn)單了。只需先通過(guò)反射獲取注解的代理對(duì)象,然后獲取該代理對(duì)象的InvocationHandler實(shí)例,最后修改AnnotationInvocationHandler實(shí)例的memberValues字段中屬性值即可。下述代碼,嘗試將attachById方法上@MyLog注解的值由DEBUG修改為WARN;而batchDelete方法上@MyLog注解的值則不會(huì)受到影響
/**
* 動(dòng)態(tài)修改注解值
*/
public class RuntimeModifyAnnoTest {
public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException {
test2();
}
public static void test2() throws NoSuchMethodException, NoSuchFieldException, IllegalAccessException {
// 獲取類的方法
Method attachByIdMethod = UserInfoService.class.getDeclaredMethod("attachById", Integer.class);
// 獲取方法的注解
MyLog attachByIdMyLog = attachByIdMethod.getDeclaredAnnotation( MyLog.class );
// 獲取注解level屬性的值
String level = attachByIdMyLog.level();
System.out.println("attachById方法 @MyLog注解 level屬性值: " + level);
//獲取該代理對(duì)象的InvocationHandler調(diào)用處理器實(shí)例
InvocationHandler invocationHandler = Proxy.getInvocationHandler( attachByIdMyLog );
// 獲取AnnotationInvocationHandler類的的私有字段 memberValues
Field memberValuesField = invocationHandler.getClass().getDeclaredField("memberValues");
// 因?yàn)?nbsp;memberValues 字段為private,故需設(shè)置為可訪問(wèn)
memberValuesField.setAccessible(true);
// 獲取 memberValues 字段的值
Map<String, Object> memberValues = (Map<String, Object>) memberValuesField.get(invocationHandler);
// 修改注解屬性為level的值
memberValues.put("level", "WARN");
// 獲取注解level屬性的值
level = attachByIdMyLog.level();
System.out.println("attachById方法 @MyLog注解 level屬性值: " + level);
// 獲取類的方法
Method batchDeleteMethod = UserInfoService.class.getDeclaredMethod("batchDelete", List.class);
// 獲取方法的注解
MyLog batchDeleteMyLog = batchDeleteMethod.getDeclaredAnnotation( MyLog.class );
// 獲取注解level屬性的值
level = batchDeleteMyLog.level();
System.out.println("batchDelete方法 @MyLog注解 level屬性值: " + level);
}
}
效果如下所示
