Java反射動(dòng)態(tài)修改注解的某個(gè)屬性值
昨晚看到一條問(wèn)題,大意是樓主希望可以動(dòng)態(tài)得建立多個(gè)Spring 的定時(shí)任務(wù)。
這個(gè)題目我并不是很熟悉,不過(guò)根據(jù)題目描述和查閱相關(guān)Spring 創(chuàng)建定時(shí)任務(wù)的資料,發(fā)現(xiàn)這也許涉及到通過(guò)Java代碼動(dòng)態(tài)修改注解的屬性值。
今天對(duì)此嘗試了一番,
發(fā)現(xiàn)通過(guò)反射來(lái)動(dòng)態(tài)修改注解的屬性值是可以做到的:
眾所周知,java/lang/reflect這個(gè)包下面都是Java的反射類和工具。
Annotation注解,也是位于這個(gè)包里的。注解自從Java 5.0版本引入后,就成為了Java平臺(tái)中非常重要的一部分,常見(jiàn)的如@Override、@Deprecated。
關(guān)于注解更詳細(xì)的信息和使用方法,網(wǎng)上已經(jīng)有很多資料,這里就不再贅述了。
一個(gè)注解通過(guò)@Retention指定其生命周期,本文所討論的動(dòng)態(tài)修改注解屬性值,建立在@Retention(RetentionPolicy.RUNTIM)這種情況。畢竟這種注解才能在運(yùn)行時(shí)(runtime)通過(guò)反射機(jī)制進(jìn)行操作。
那么現(xiàn)在我們定義一個(gè)@Foo注解,它有一個(gè)類型為String的value屬性,該注解應(yīng)用再Field上:
1
2
3
4
5
6
7
8
|
/** * Created by krun on 2017/9/18. */ @Target (ElementType.FIELD) @Retention (RetentionPolicy.RUNTIME) public @interface Foo { String value(); } |
再定義一個(gè)普通的Java對(duì)象Bar,它有一個(gè)私有的String屬性val,并為它設(shè)置屬性值為"fff"的@Foo注解:
1
2
3
4
5
|
public class Bar { @Foo ( "fff" ) private String val; } |
接下來(lái)在main方法中我們來(lái)嘗試修改Bar.val上的@Foo注解的屬性值為"ddd"。
先是正常的獲取注解屬性值:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
/** * Created by krun on 2017/9/18. */ public class Main { public static void main(String ...args) throws NoSuchFieldException { //獲取Bar實(shí)例 Bar bar = new Bar(); //獲取Bar的val字段 Field field = Bar. class .getDeclaredField( "val" ); //獲取val字段上的Foo注解實(shí)例 Foo foo = field.getAnnotation(Foo. class ); //獲取Foo注解實(shí)例的 value 屬性值 String value = foo.value(); //打印該值 System.out.println(value); // fff } } |
首先,我們要知道注解的值是存在哪里的。
在String value = foo.value();處下斷點(diǎn),我們跑一下可以發(fā)現(xiàn):
當(dāng)前棧中有這么幾個(gè)變量,不過(guò)其中有一點(diǎn)很特別:foo,其實(shí)是個(gè)Proxy實(shí)例。
Proxy也是java/lang/reflect下的東西,它的作用是為一個(gè)Java類生成一個(gè)代理,就像這樣:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
public interface A { String func1(); } public class B implements A { @Override public String func1() { //do something ... } public String func2() { //do something ... }; } public static void main(String ...args) { B bInstance = new B(); B bProxy = Proxy.newProxyInstance( B. class .getClassLoader(), // B 類的類加載器 B. class .getInterfaces(), // B 類所實(shí)現(xiàn)的接口,如果你想攔截B類的某個(gè)方法,必須讓這個(gè)方法在某個(gè)接口中聲明并讓B類實(shí)現(xiàn)該接口 new InvocationHandler() { // 調(diào)用處理器,任何對(duì) B類所實(shí)現(xiàn)的接口方法的調(diào)用都會(huì)觸發(fā)此處理器 @Override public Object invoke (Object proxy, // 這個(gè)是代理的實(shí)例,method.invoke時(shí)不能使用這個(gè),否則會(huì)死循環(huán) Method method, // 觸發(fā)的接口方法 Object[] args // 此次調(diào)用該方法的參數(shù) ) throws Throwable { System.out.println(String.format( "調(diào)用 %s 之前" , method.getName())); /** * 這里必須使用B類的某個(gè)具體實(shí)現(xiàn)類的實(shí)例,因?yàn)橛|發(fā)時(shí)這里的method只是一個(gè)接口方法的引用, * 也就是說(shuō)它是空的,你需要為它指定具有邏輯的上下文(bInstance)。 */ Object obj = method.invoke(bInstance, args); System.out.println(String.format( "調(diào)用 %s 之后" , method.getName())); return obj; //返回調(diào)用結(jié)果 } } ); } |
這樣你就可以攔截這個(gè)Java類的某個(gè)方法調(diào)用,但是你只能攔截到func1的調(diào)用,想想為什么?
那么注意了:
ClassLoader這是個(gè)class就會(huì)有,注解也不例外。那么注解和interfaces有什么關(guān)系?
注解本質(zhì)上就是一個(gè)接口,它的實(shí)質(zhì)定義為:interface SomeAnnotation extends Annotation。這個(gè)Annotation接口位于java/lang/annotation包,它的注釋中第一句話就是The common interface extended by all annotation types.
如此說(shuō)來(lái),F(xiàn)oo注解本身只是個(gè)接口,這就意味著它沒(méi)有任何代碼邏輯,那么它的value屬性究竟是存在哪里的呢?
展開(kāi)foo可以發(fā)現(xiàn):
這個(gè)Proxy實(shí)例持有一個(gè)AnnotationInvocationHandler,還記得之前提到過(guò)如何創(chuàng)建一個(gè)Proxy實(shí)例么? 第三個(gè)參數(shù)就是一個(gè)InvocationHandler。
看名字這個(gè)handler即是Annotation所特有的,我們看一下它的代碼:
1
2
3
4
5
6
|
class AnnotationInvocationHandler implements InvocationHandler, Serializable { private final Class<? extends Annotation> type; private final Map<String, Object> memberValues; private transient volatile Method[] memberMethods = null ; /* 后續(xù)無(wú)關(guān)代碼就省略了,想看的話可以查看 sun/reflect/annotation/AnnotationInvocationHandler */ } |
我們一眼就可以看到一個(gè)有意思的名字:memberValues,這是一個(gè)Map,而斷點(diǎn)中可以看到這是一個(gè)LinknedHashMap,key為注解的屬性名稱,value即為注解的屬性值。
現(xiàn)在我們找到了注解的屬性值存在哪里了,那么接下來(lái)的事就好辦了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
/** * Created by krun on 2017/9/18. */ public class Main { public static void main(String ...args) throws NoSuchFieldException, IllegalAccessException { //獲取Bar實(shí)例 Bar bar = new Bar(); //獲取Bar的val字段 Field field = Bar. class .getDeclaredField( "val" ); //獲取val字段上的Foo注解實(shí)例 Foo foo = field.getAnnotation(Foo. class ); //獲取 foo 這個(gè)代理實(shí)例所持有的 InvocationHandler InvocationHandler h = Proxy.getInvocationHandler(foo); // 獲取 AnnotationInvocationHandler 的 memberValues 字段 Field hField = h.getClass().getDeclaredField( "memberValues" ); // 因?yàn)檫@個(gè)字段事 private final 修飾,所以要打開(kāi)權(quán)限 hField.setAccessible( true ); // 獲取 memberValues Map memberValues = (Map) hField.get(h); // 修改 value 屬性值 memberValues.put( "value" , "ddd" ); // 獲取 foo 的 value 屬性值 String value = foo.value(); System.out.println(value); // ddd } } |
通過(guò)反射動(dòng)態(tài)修改自定義注解屬性值
java/lang/reflect 這個(gè)包下面都是Java的反射類和工具。
Annotation 注解,也是位于這個(gè)包里的。
注解自從Java 5.0版本引入后,就成為了Java平臺(tái)中非常重要的一部分,常見(jiàn)的有 @Override、 @Deprecated
關(guān)于注解更詳細(xì)的信息和使用方法,網(wǎng)上已經(jīng)有很多資料,自行查看。
一個(gè)注解通過(guò) @Retention 指定其生命周期,本文所討論的動(dòng)態(tài)修改注解屬性值,建立在 @Retention(RetentionPolicy.RUNTIM) 這種情況。
這種注解才能在運(yùn)行時(shí)(runtime)通過(guò)反射機(jī)制進(jìn)行修改屬性的操作。
我們先定義一個(gè)自定義注解 @TestAnno
它有一個(gè)類型為 String 的 name屬性,該注解應(yīng)用再M(fèi)ethod上:
1
2
3
4
5
6
|
@Target ({ ElementType.METHOD }) @Retention (RetentionPolicy.RUNTIME) @Component public @interface TestAnno { String name() default "" ; } |
我用自定義注解首先得了解清楚注解的值存儲(chǔ)在什么地方,我們可以寫(xiě)個(gè)main方法測(cè)試一下:
通過(guò)反射獲取注解@TestAnno的值
我們定義了一個(gè)RetryTestService 在它的方法 retryTest() 上添加@TestAnno 注解,然后在main方法里面反射獲取注解的name值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@Service public class RetryTestService { @TimeLog @TestAnno (name = "${nba.kobe}" ) public String retryTest(){ System.out.println( "---進(jìn)行了接口請(qǐng)求...." ); return "success" ; } public static void main(String[] args) throws NoSuchMethodException { RetryTestService service = new RetryTestService(); Method method = service.getClass().getDeclaredMethod( "retryTest" , null ); TestAnno testAnno = method.getDeclaredAnnotation(TestAnno. class ); System.out.println(testAnno.name()); } } |
當(dāng)前棧中有這么幾個(gè)變量,不過(guò)其中有一點(diǎn)很特別:@TestAnno,其實(shí)是個(gè)Proxy實(shí)例。
Proxy也是 java/lang/reflect下的東西,它的作用是為一個(gè)Java類生成一個(gè)代理,就像這樣:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
public interface A { String func1(); } public class B implements A { @Override public String func1() { //do something ... } public String func2() { //do something ... }; } public static void main(String ...args) { B bInstance = new B(); B bProxy = Proxy.newProxyInstance( B. class .getClassLoader(), // B 類的類加載器 B. class .getInterfaces(), // B 類所實(shí)現(xiàn)的接口,如果你想攔截B類的某個(gè)方法,必須讓這個(gè)方法在某個(gè)接口中聲明并讓B類實(shí)現(xiàn)該接口 new InvocationHandler() { // 調(diào)用處理器,任何對(duì) B類所實(shí)現(xiàn)的接口方法的調(diào)用都會(huì)觸發(fā)此處理器 @Override public Object invoke (Object proxy, // 這個(gè)是代理的實(shí)例,method.invoke時(shí)不能使用這個(gè),否則會(huì)死循環(huán) Method method, // 觸發(fā)的接口方法 Object[] args // 此次調(diào)用該方法的參數(shù) ) throws Throwable { System.out.println(String.format( "調(diào)用 %s 之前" , method.getName())); /** * 這里必須使用B類的某個(gè)具體實(shí)現(xiàn)類的實(shí)例,因?yàn)橛|發(fā)時(shí)這里的method只是一個(gè)接口方法的引用, * 也就是說(shuō)它是空的,你需要為它指定具有邏輯的上下文(bInstance)。 */ Object obj = method.invoke(bInstance, args); System.out.println(String.format( "調(diào)用 %s 之后" , method.getName())); return obj; //返回調(diào)用結(jié)果 } } ); } |
注意了:
ClassLoader 這是個(gè)class就會(huì)有,注解也不例外。那么注解和interfaces有什么關(guān)系?
注解本質(zhì)上就是一個(gè)接口,它的實(shí)質(zhì)定義為: interface SomeAnnotation extends Annotation。
這個(gè) Annotation 接口位于 java/lang/annotation 包,它的注釋中第一句話就是 The common interface extended by all annotation types.
如此說(shuō)來(lái),@TestAnno 注解本身只是個(gè)接口,這就意味著它沒(méi)有任何代碼邏輯,那么它的 value 屬性究竟是存在哪里的呢?
展開(kāi) @TestAnno 可以發(fā)現(xiàn):
這個(gè) Proxy 實(shí)例持有一個(gè) AnnotationInvocationHandler,還記得之前提到過(guò)如何創(chuàng)建一個(gè) Proxy 實(shí)例么? 第三個(gè)參數(shù)就是一個(gè) InvocationHandler。
看名字這個(gè)handler即是Annotation所特有的,我們看一下它的代碼:
1
2
3
4
5
6
7
8
|
class AnnotationInvocationHandler implements InvocationHandler, Serializable { private final Class<? extends Annotation> type; private final Map<String, Object> memberValues; private transient volatile Method[] memberMethods = null ; /* 后續(xù)無(wú)關(guān)代碼就省略了,想看的話可以查看 sun/reflect/annotation/AnnotationInvocationHandler */ } |
我們一眼就可以看到一個(gè)有意思的名字: memberValues,這是一個(gè)Map,而斷點(diǎn)中可以看到這是一個(gè) LinknedHashMap,key為注解的屬性名稱,value即為注解的屬性值。
現(xiàn)在我們找到了注解的屬性值存在哪里了,那么接下來(lái)的事就好辦了:
我這里寫(xiě)兩個(gè)aop。第一個(gè)aop攔截帶@TestAnno注解的方法,然后改變注解的name值,第二個(gè)aop我們?cè)侔炎⒔獾膎ame值打印出來(lái),看看是不是真被改了
第一個(gè)aop:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
@Aspect @Component @Order ( 1 ) //aop執(zhí)行順序1表示先執(zhí)行此aop public class AuthDemoAspect implements EnvironmentAware { Environment environment; @Override public void setEnvironment(Environment environment) { this .environment = environment; } @Pointcut ( "@annotation(com.ali.hangz.tooltest.config.TestAnno)" ) public void myPointCut() { } @Before (value = "myPointCut()" ) public void check(){ } @After (value = "myPointCut()" ) public void bye(){ } /** *配置文件配置 * @return */ @Around ( "myPointCut() && @annotation(testAnno)" ) public Object around(ProceedingJoinPoint joinPoint, TestAnno testAnno){ try { System.out.println( "---修改前注解@TestAnno的name指為:" + testAnno.name()); String s = environment.resolvePlaceholders(testAnno.name()); //獲取 foo 這個(gè)代理實(shí)例所持有的 InvocationHandler InvocationHandler h = Proxy.getInvocationHandler(testAnno); // 獲取 AnnotationInvocationHandler 的 memberValues 字段 Field hField = h.getClass().getDeclaredField( "memberValues" ); // 因?yàn)檫@個(gè)字段事 private final 修飾,所以要打開(kāi)權(quán)限 hField.setAccessible( true ); // 獲取 memberValues Map memberValues = (Map) hField.get(h); // 修改 value 屬性值 memberValues.put( "name" ,s); return joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } return null ; } } |
第一個(gè)aop里面我改變注解的name值,由上面service方法上注解的${nba.kobe} 改成讀取配置文件 nba.kobe的配置值
項(xiàng)目配置文件:application.properties增加一個(gè)值
1
2
|
nba.kobe=科比 String s = environment.resolvePlaceholders(testAnno.name()); |
這行代碼其實(shí)就是通過(guò)原本注解值${nba.kobe}去配置文件取nba.kobe 對(duì)應(yīng)的值。如果你只是修改原來(lái)注解的name值而不是去取配置文件大可以不用此行代碼,直接給memberValues 里面的name put新的值就行。
注意:@Order(1) 可以控制aop的執(zhí)行順序
然后我再寫(xiě)第二個(gè)aop,打印出注解@TestAnno 的name值看看是不是第一個(gè)aop已經(jīng)成功把值改掉了
第二個(gè)aop:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
@Aspect @Component @Order ( 2 ) public class AuthDemoAspectTwo implements EnvironmentAware { Environment environment; @Override public void setEnvironment(Environment environment) { this .environment = environment; } @Pointcut ( "@annotation(com.ali.hangz.tooltest.config.TestAnno)" ) public void myPointCut() { } @Before (value = "myPointCut()" ) public void check(){ } @After (value = "myPointCut()" ) public void bye(){ } /** *配置文件配置 * @return */ @Around ( "myPointCut() && @annotation(testAnno)" ) public Object around(ProceedingJoinPoint joinPoint, TestAnno testAnno){ try { System.out.println( "---修改后的注解名稱:" + testAnno.name()); return joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } return null ; } |
然后我們只需要啟動(dòng)項(xiàng)目調(diào)用一下RetryTestService的 retryTest()方法 就可以進(jìn)入aop 看看打印出來(lái)的結(jié)果了
通過(guò)結(jié)果我們可以發(fā)現(xiàn)第一個(gè)aop的確把retryTest()方法上面注解@TestAnno的name值由原先的 @TestAnno(name = "${nba.kobe}") ${nba.kobe}值動(dòng)態(tài)修改成了配置文件里面配置的“科比”了。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持服務(wù)器之家。
原文鏈接:https://segmentfault.com/a/1190000011213222