1. 程式人生 > >[Java] 通過反射,動態修改註解的某個屬性值

[Java] 通過反射,動態修改註解的某個屬性值

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

這個題目我並不是很熟悉,不過根據題目描述和查閱相關 Spring 建立定時任務 的資料,發現這也許涉及到通過Java程式碼動態修改註解的屬性值。

今天對此嘗試了一番,發現通過反射來動態修改註解的屬性值是可以做到的:

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

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

關於註解更詳細的資訊和使用方法,網上已經有很多資料,這裡就不再贅述了。

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

那麼現在我們定義一個 @Foo 註解,它有一個型別為 String 的 value 屬性,該註解應用再Field上:


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

再定義一個普通的Java物件 Bar,它有一個私有的String屬性 val,併為它設定屬性值為"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(); 處下斷點,我們跑一下可以發現:

當前棧中有這麼幾個變數,不過其中有一點很特別: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 類所實現的介面,如果你想攔截B類的某個方法,必須讓這個方法在某個介面中宣告並讓B類實現該介面
        new InvocationHandler() { // 呼叫處理器,任何對 B類所實現的介面方法的呼叫都會觸發此處理器
            @Override
            public Object invoke (Object proxy, // 這個是代理的例項,method.invoke時不能使用這個,否則會死迴圈
                                  Method method, // 觸發的介面方法
                                  Object[] args // 此次呼叫該方法的引數
                                  ) throws Throwable {
                System.out.println(String.format("呼叫 %s 之前", method.getName()));
                /**
                 * 這裡必須使用B類的某個具體實現類的例項,因為觸發時這裡的method只是一個介面方法的引用,
                 * 也就是說它是空的,你需要為它指定具有邏輯的上下文(bInstance)。
                 */
                Object obj = method.invoke(bInstance, args); 
                System.out.println(String.format("呼叫 %s 之後", method.getName()));
                return obj; //返回呼叫結果
            }
        }
    );
}

這樣你就可以攔截這個Java類的某個方法呼叫,但是你只能攔截到 func1的呼叫,想想為什麼?

那麼注意了:

ClassLoader 這是個class就會有,註解也不例外。那麼註解和interfaces有什麼關係?

註解本質上就是一個介面,它的實質定義為: interface SomeAnnotation extends Annotation
這個 Annotation 介面位於 java/lang/annotation 包,它的註釋中第一句話就是 The common interface extended by all annotation types.

如此說來,Foo 註解本身只是個介面,這就意味著它沒有任何程式碼邏輯,那麼它的 value 屬性究竟是存在哪裡的呢?

展開 foo 可以發現:

這個 Proxy 例項持有一個 AnnotationInvocationHandler,還記得之前提到過如何建立一個 Proxy 例項麼? 第三個引數就是一個 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;
    
    /* 後續無關程式碼就省略了,想看的話可以檢視 sun/reflect/annotation/AnnotationInvocationHandler */
   
}

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

現在我們找到了註解的屬性值存在哪裡了,那麼接下來的事就好辦了:

/**
 * 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 修飾,所以要開啟許可權
        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
    }


相關推薦

[Java] 通過反射動態修改註解某個屬性

昨晚看到一條問題,大意是樓主希望可以動態得建立多個Spring 的定時任務。這個題目我並不是很熟悉,不過根據題目描述和查閱相關 Spring 建立定時任務 的資料,發現這也許涉及到通過Java程式碼動態修改註解的屬性值。今天對此嘗試了一番,發現通過反射來動態修改註解的屬性值是

通過js動態修改樣式表

var style = document.createElement('style');style.type = 'text/css';style.innerHTML=``;document.getElementsByTagName('HEAD').item(0).appen

java通過反射只需要傳了類名和引數就可以根據不同引數的構造方法例項化物件

轉載自:http://www.jianshu.com/p/69ca44916ebf 程式碼塊 @requires_authorization private Object reflateInstance(String className, Object[] args)th

java通過反射+javassist獲得方法所有資訊(返回、方法名、引數型別列表、引數列表)

眾所周知,使用java的反射無法獲得方法引數名列表,只能獲得方法引數型別列表,在網上研究了一下,發現有下面兩種方式實現: 方案一:使用反射+javassit庫static void javassist

Java反射動態修改註解

先來看看通常情況下,我們通過反射獲取註解的值的場景:那麼現在我們定義一個 @Foo 註解,它有一個型別為 String 的 value 屬性,該註解應用再Field上:/** * @Author 落葉飛翔的蝸牛 * @Date 2018/3/11 * @Descript

Java 自定義註解&通過反射獲取類、方法、屬性上的註解

反射 JAVA中的反射是執行中的程式檢查自己和軟體執行環境的能力,它可以根據它發現的進行改變。通俗的講就是反射可以在執行時根據指定的類名獲得類的資訊。   註解的定義 註解通過 @interface 關鍵字進行定義。 /** * 自定義註解 *

java 通過反射獲取註解

使用反射獲取某個類的註釋、方法上的註釋、屬性上的註釋。 import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import javax.x

Java通過jacob生成動態word還有一種方法通過word書籤實現

多個word合併 先下載jacob.jar包。 如果是32位系統 將jacob-1.18-x86.dll 檔案複製到下面目錄下,如果是64位作業系統 將jacob-1.18-x64.dll C:\Program Files\Java\jdk1.8.0_45\jre\bi

java通過類的反射獲取頁面修改欄位前後以及欄位名稱

public void saveChangeVolue(Object before, Object after,String log) { StringBuffer alterContent=new StringBuffer(); //before和after

java通過反射獲取類名、屬性名稱以及@Table註解上的表名稱

import javax.persistence.Column; import javax.persistence.Id; import javax.persistence.Table; import java.lang.reflect.Field; import java.

通過反射後臺時時儲存修改的資料

之前做的時時修改都是給各型別一個編號,後臺通過編號然後if去判別,現在看來當時的方法有點low,現給出新方法 import java.lang.reflect.Field; String field = request.getParameter

java通過反射獲取方法的引數名Idea/Eclipse/Maven的配置

1,在8以前的jdk版本中,我們利用反射只能獲取到引數型別,然後引數名字都是利用arg0,arg1,arg2......要想獲得引數名,得加上註解,如下:public User getUser(@Param("groupid") String groupid, @Param(

java 通過反射和自定義泛型來修改物件

public static <T> T Update(T beforeUpdate,T update){ try { if(beforeUpdate==null) return update; if(update==nul

AOP通過反射獲取自定義註解

ram tar .get tty sig runt type log eth 自定義註解: @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component

利用java反射實現工廠創建對象

傳遞 之前 illegal stack bject 運行時 做的 ace exc public static Object getInstance(Class c){ Object obj = null; try {

用C#通過反射實現動態調用WebService 告別Web引用(轉載)

sse data 語言 con classname ext 級別 creat 字符 我們都知道,調用WebService可以在工程中對WebService地址進行WEB引用,但是這確實很不方便。我想能夠利用配置文件靈活調用WebService。如何實現呢? 用C#通過反射

java通過反射獲取私有的構造方法,及反射擦除泛型數據類型約束

code for循環 getmethod 不能 添加 class win flag ring /* * 反射獲取私有的構造方法運行 * 不推薦,破壞了程序的安全性,封裝性 * 暴力私有 */ public class ReflectDemo4 {

基礎 | Java反射動態代理

關於「反射」請參看Class類詳解(反射)部分。 動態代理作為Java反射的主要應用之一,其在多種JavaEE框架中均有使用,如Spring框架中AOP的實現原理就是動態代理,面試中提到AOP也必定會問 「談談對動態代理的理解?」 相關問題,在此做一個梳理與總結。 談

java 通過反射獲取泛型的型別

分享一下我老師大神的人工智慧教程吧。零基礎,通俗易懂!風趣幽默!http://www.captainbed.net/ 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Java通過反射建立JDBC操作資料庫的通用方法

JAVA反射機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意方法和屬性;這種動態獲取資訊以及動態呼叫物件方法的功能稱為java語言的反射機制。 首先我們看個反射的小例子。 1.我們新建一個User的類。 2.通過反射獲取User物