欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java通過反射,如何動態(tài)修改注解的某個屬性值

 更新時間:2021年07月15日 09:36:52   作者:krun  
這篇文章主要介紹了Java通過反射,動態(tài)修改注解的某個屬性值操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

Java反射動態(tài)修改注解的某個屬性值

昨晚看到一條問題,大意是樓主希望可以動態(tài)得建立多個Spring 的定時任務(wù)。

這個題目我并不是很熟悉,不過根據(jù)題目描述和查閱相關(guān)Spring 創(chuàng)建定時任務(wù)的資料,發(fā)現(xiàn)這也許涉及到通過Java代碼動態(tài)修改注解的屬性值。

今天對此嘗試了一番,

發(fā)現(xiàn)通過反射來動態(tài)修改注解的屬性值是可以做到的:

眾所周知,java/lang/reflect這個包下面都是Java的反射類和工具。

Annotation注解,也是位于這個包里的。注解自從Java 5.0版本引入后,就成為了Java平臺中非常重要的一部分,常見的如@Override、@Deprecated。

關(guān)于注解更詳細的信息和使用方法,網(wǎng)上已經(jīng)有很多資料,這里就不再贅述了。

一個注解通過@Retention指定其生命周期,本文所討論的動態(tài)修改注解屬性值,建立在@Retention(RetentionPolicy.RUNTIM)這種情況。畢竟這種注解才能在運行時(runtime)通過反射機制進行操作。

那么現(xiàn)在我們定義一個@Foo注解,它有一個類型為String的value屬性,該注解應(yīng)用再Field上:

/**
 * Created by krun on 2017/9/18.
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Foo {
    String value();
}

再定義一個普通的Java對象Bar,它有一個私有的String屬性val,并為它設(shè)置屬性值為"fff"的@Foo注解:

public class Bar {
 
    @Foo ("fff")
    private String val;
}

接下來在main方法中我們來嘗試修改Bar.val上的@Foo注解的屬性值為"ddd"。

先是正常的獲取注解屬性值:

/**
 * Created by krun on 2017/9/18.
 */
public class Main {
    public static void main(String ...args) throws NoSuchFieldException {
        //獲取Bar實例
        Bar bar = new Bar();
        //獲取Bar的val字段
        Field field = Bar.class.getDeclaredField("val");
        //獲取val字段上的Foo注解實例
        Foo foo = field.getAnnotation(Foo.class);
        //獲取Foo注解實例的 value 屬性值
        String value = foo.value();
        //打印該值
        System.out.println(value); // fff
    }
}

首先,我們要知道注解的值是存在哪里的。

在String value = foo.value();處下斷點,我們跑一下可以發(fā)現(xiàn):

當前棧中有這么幾個變量,不過其中有一點很特別:foo,其實是個Proxy實例。

Proxy也是java/lang/reflect下的東西,它的作用是為一個Java類生成一個代理,就像這樣:

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 類所實現(xiàn)的接口,如果你想攔截B類的某個方法,必須讓這個方法在某個接口中聲明并讓B類實現(xiàn)該接口
        new InvocationHandler() { // 調(diào)用處理器,任何對 B類所實現(xiàn)的接口方法的調(diào)用都會觸發(fā)此處理器
            @Override
            public Object invoke (Object proxy, // 這個是代理的實例,method.invoke時不能使用這個,否則會死循環(huán)
                                  Method method, // 觸發(fā)的接口方法
                                  Object[] args // 此次調(diào)用該方法的參數(shù)
                                  ) throws Throwable {
                System.out.println(String.format("調(diào)用 %s 之前", method.getName()));
                /**
                 * 這里必須使用B類的某個具體實現(xiàn)類的實例,因為觸發(fā)時這里的method只是一個接口方法的引用,
                 * 也就是說它是空的,你需要為它指定具有邏輯的上下文(bInstance)。
                 */
                Object obj = method.invoke(bInstance, args);
                System.out.println(String.format("調(diào)用 %s 之后", method.getName()));
                return obj; //返回調(diào)用結(jié)果
            }
        }
    );
}

這樣你就可以攔截這個Java類的某個方法調(diào)用,但是你只能攔截到func1的調(diào)用,想想為什么?

那么注意了:

ClassLoader這是個class就會有,注解也不例外。那么注解和interfaces有什么關(guān)系?

注解本質(zhì)上就是一個接口,它的實質(zhì)定義為:interface SomeAnnotation extends Annotation。這個Annotation接口位于java/lang/annotation包,它的注釋中第一句話就是The common interface extended by all annotation types.

如此說來,F(xiàn)oo注解本身只是個接口,這就意味著它沒有任何代碼邏輯,那么它的value屬性究竟是存在哪里的呢?

展開foo可以發(fā)現(xiàn):

這個Proxy實例持有一個AnnotationInvocationHandler,還記得之前提到過如何創(chuàng)建一個Proxy實例么? 第三個參數(shù)就是一個InvocationHandler。

看名字這個handler即是Annotation所特有的,我們看一下它的代碼:

class AnnotationInvocationHandler implements InvocationHandler, Serializable { 
    private final Class<? extends Annotation> type;
    private final Map<String, Object> memberValues;
    private transient volatile Method[] memberMethods = null;     
    /* 后續(xù)無關(guān)代碼就省略了,想看的話可以查看 sun/reflect/annotation/AnnotationInvocationHandler */    
}

我們一眼就可以看到一個有意思的名字:memberValues,這是一個Map,而斷點中可以看到這是一個LinknedHashMap,key為注解的屬性名稱,value即為注解的屬性值。

現(xiàn)在我們找到了注解的屬性值存在哪里了,那么接下來的事就好辦了:

/**
 * Created by krun on 2017/9/18.
 */
public class Main {
    public static void main(String ...args) throws NoSuchFieldException, IllegalAccessException {
        //獲取Bar實例
        Bar bar = new Bar();
        //獲取Bar的val字段
        Field field = Bar.class.getDeclaredField("val");
        //獲取val字段上的Foo注解實例
        Foo foo = field.getAnnotation(Foo.class);
        //獲取 foo 這個代理實例所持有的 InvocationHandler
        InvocationHandler h = Proxy.getInvocationHandler(foo);
        // 獲取 AnnotationInvocationHandler 的 memberValues 字段
        Field hField = h.getClass().getDeclaredField("memberValues");
        // 因為這個字段事 private final 修飾,所以要打開權(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
    }
}

通過反射動態(tài)修改自定義注解屬性值

java/lang/reflect 這個包下面都是Java的反射類和工具。

Annotation 注解,也是位于這個包里的。

注解自從Java 5.0版本引入后,就成為了Java平臺中非常重要的一部分,常見的有 @Override、 @Deprecated

關(guān)于注解更詳細的信息和使用方法,網(wǎng)上已經(jīng)有很多資料,自行查看。

一個注解通過 @Retention 指定其生命周期,本文所討論的動態(tài)修改注解屬性值,建立在 @Retention(RetentionPolicy.RUNTIM) 這種情況。

這種注解才能在運行時(runtime)通過反射機制進行修改屬性的操作。

我們先定義一個自定義注解 @TestAnno

它有一個類型為 String 的 name屬性,該注解應(yīng)用再Method上:

@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface TestAnno { 
   String name() default ""; 
}

我用自定義注解首先得了解清楚注解的值存儲在什么地方,我們可以寫個main方法測試一下:

通過反射獲取注解@TestAnno的值

我們定義了一個RetryTestService 在它的方法 retryTest() 上添加@TestAnno 注解,然后在main方法里面反射獲取注解的name值

@Service
public class RetryTestService { 
    @TimeLog
    @TestAnno(name = "${nba.kobe}")
    public String retryTest(){
        System.out.println("---進行了接口請求....");
        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());
    }
}

當前棧中有這么幾個變量,不過其中有一點很特別:@TestAnno,其實是個Proxy實例。

Proxy也是 java/lang/reflect下的東西,它的作用是為一個Java類生成一個代理,就像這樣:

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 類所實現(xiàn)的接口,如果你想攔截B類的某個方法,必須讓這個方法在某個接口中聲明并讓B類實現(xiàn)該接口
        new InvocationHandler() { // 調(diào)用處理器,任何對 B類所實現(xiàn)的接口方法的調(diào)用都會觸發(fā)此處理器
            @Override
            public Object invoke (Object proxy, // 這個是代理的實例,method.invoke時不能使用這個,否則會死循環(huán)
                                  Method method, // 觸發(fā)的接口方法
                                  Object[] args // 此次調(diào)用該方法的參數(shù)
                                  ) throws Throwable {
                System.out.println(String.format("調(diào)用 %s 之前", method.getName()));
                /**
                 * 這里必須使用B類的某個具體實現(xiàn)類的實例,因為觸發(fā)時這里的method只是一個接口方法的引用,
                 * 也就是說它是空的,你需要為它指定具有邏輯的上下文(bInstance)。
                 */
                Object obj = method.invoke(bInstance, args);
                System.out.println(String.format("調(diào)用 %s 之后", method.getName()));
                return obj; //返回調(diào)用結(jié)果
            }
        }
    );
}

注意了:

ClassLoader 這是個class就會有,注解也不例外。那么注解和interfaces有什么關(guān)系?

注解本質(zhì)上就是一個接口,它的實質(zhì)定義為: interface SomeAnnotation extends Annotation。

這個 Annotation 接口位于 java/lang/annotation 包,它的注釋中第一句話就是 The common interface extended by all annotation types.

如此說來,@TestAnno 注解本身只是個接口,這就意味著它沒有任何代碼邏輯,那么它的 value 屬性究竟是存在哪里的呢?

展開 @TestAnno 可以發(fā)現(xiàn):

這個 Proxy 實例持有一個 AnnotationInvocationHandler,還記得之前提到過如何創(chuàng)建一個 Proxy 實例么? 第三個參數(shù)就是一個 InvocationHandler。

看名字這個handler即是Annotation所特有的,我們看一下它的代碼:

class AnnotationInvocationHandler implements InvocationHandler, Serializable {
    private final Class<? extends Annotation> type;
    private final Map<String, Object> memberValues;
    private transient volatile Method[] memberMethods = null;
    
    /* 后續(xù)無關(guān)代碼就省略了,想看的話可以查看 sun/reflect/annotation/AnnotationInvocationHandler */
   
}

我們一眼就可以看到一個有意思的名字: memberValues,這是一個Map,而斷點中可以看到這是一個 LinknedHashMap,key為注解的屬性名稱,value即為注解的屬性值。

現(xiàn)在我們找到了注解的屬性值存在哪里了,那么接下來的事就好辦了:

我這里寫兩個aop。第一個aop攔截帶@TestAnno注解的方法,然后改變注解的name值,第二個aop我們再把注解的name值打印出來,看看是不是真被改了

第一個aop:

@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 這個代理實例所持有的 InvocationHandler
            InvocationHandler h = Proxy.getInvocationHandler(testAnno);
            // 獲取 AnnotationInvocationHandler 的 memberValues 字段
            Field hField = h.getClass().getDeclaredField("memberValues");
            // 因為這個字段事 private final 修飾,所以要打開權(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;
    }
}

第一個aop里面我改變注解的name值,由上面service方法上注解的${nba.kobe} 改成讀取配置文件 nba.kobe的配置值

項目配置文件:application.properties增加一個值

nba.kobe=科比
String s = environment.resolvePlaceholders(testAnno.name());

這行代碼其實就是通過原本注解值${nba.kobe}去配置文件取nba.kobe 對應(yīng)的值。如果你只是修改原來注解的name值而不是去取配置文件大可以不用此行代碼,直接給memberValues 里面的name put新的值就行。

注意:@Order(1) 可以控制aop的執(zhí)行順序

然后我再寫第二個aop,打印出注解@TestAnno 的name值看看是不是第一個aop已經(jīng)成功把值改掉了

第二個aop:

@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;
    }

然后我們只需要啟動項目調(diào)用一下RetryTestService的 retryTest()方法 就可以進入aop 看看打印出來的結(jié)果了

通過結(jié)果我們可以發(fā)現(xiàn)第一個aop的確把retryTest()方法上面注解@TestAnno的name值由原先的 @TestAnno(name = "${nba.kobe}") ${nba.kobe}值動態(tài)修改成了配置文件里面配置的“科比”了。

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java數(shù)據(jù)結(jié)構(gòu)之加權(quán)無向圖的設(shè)計實現(xiàn)

    Java數(shù)據(jù)結(jié)構(gòu)之加權(quán)無向圖的設(shè)計實現(xiàn)

    加權(quán)無向圖是一種為每條邊關(guān)聯(lián)一個權(quán)重值或是成本的圖模型。這種圖能夠自然地表示許多應(yīng)用。這篇文章主要介紹了加權(quán)無向圖的設(shè)計與實現(xiàn),感興趣的可以了解一下
    2022-11-11
  • Java實現(xiàn)Flappy Bird游戲源碼

    Java實現(xiàn)Flappy Bird游戲源碼

    這篇文章主要為大家詳細介紹了Java實現(xiàn)Flappy Bird游戲源碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-12-12
  • JPA中JpaRepository接口的使用方式

    JPA中JpaRepository接口的使用方式

    這篇文章主要介紹了JPA中JpaRepository接口的使用方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • SpringBoot+Tess4j實現(xiàn)牛的OCR識別工具的示例代碼

    SpringBoot+Tess4j實現(xiàn)牛的OCR識別工具的示例代碼

    這篇文章主要介紹了SpringBoot+Tess4j實現(xiàn)牛的OCR識別工具的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • java 實現(xiàn)當前時間加減30分鐘的時間代碼

    java 實現(xiàn)當前時間加減30分鐘的時間代碼

    這篇文章主要介紹了java 實現(xiàn)當前時間加減30分鐘的時間代碼,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08
  • SpringBoot中@Scheduled實現(xiàn)服務(wù)啟動時執(zhí)行一次

    SpringBoot中@Scheduled實現(xiàn)服務(wù)啟動時執(zhí)行一次

    本文主要介紹了SpringBoot中@Scheduled實現(xiàn)服務(wù)啟動時執(zhí)行一次,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-08-08
  • java讀取xml配置參數(shù)代碼實例

    java讀取xml配置參數(shù)代碼實例

    這篇文章主要介紹了java讀取xml配置參數(shù)代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-12-12
  • Java中Future接口詳解

    Java中Future接口詳解

    這篇文章主要介紹了Java中Future接口詳解,本文通過案例給大家詳細講解了Java中Future接口,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-04-04
  • springboot啟動過程中常用的回調(diào)示例詳解

    springboot啟動過程中常用的回調(diào)示例詳解

    springboot提供非常豐富回調(diào)接口,利用這些接口可以做非常多的事情,本文通過實例代碼給大家介紹springboot啟動過程中常用的回調(diào)知識感興趣的朋友跟隨小編一起看看吧
    2022-01-01
  • Java后臺線程操作示例【守護線程】

    Java后臺線程操作示例【守護線程】

    這篇文章主要介紹了Java后臺線程操作,結(jié)合實例形式分析了java守護線程相關(guān)原理、操作技巧與使用注意事項,需要的朋友可以參考下
    2019-09-09

最新評論