自定義註解+反射 實現給註解新增功能的效果

詩水人間發表於2020-12-01

註解我們經常會用到,或者在jdk原始碼中也會看到,例如: @Deprecated
以及我們在spring或者springboot中經常用到@Controller、@Service、@Repository、@Entity等註解。

是否思考過他們是怎麼工作的?

下面我們使用 自定義註解 + 反射給註解加上功能

先貼出整體效果圖:
在這裡插入圖片描述
原始碼:java8環境

一、定義註解

jdk提供了自定義註解的工具類,在 java.lang.annotation包下

先看下自定義註解的模板:(模板後面有解釋)

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME) // 註解
@Target({ElementType.FIELD,ElementType.METHOD}) // 定義 @MyAnnotation註解能加在哪些地方,這裡表示可以加在欄位和方法上
public @interface MyAnnotation {

    String value() default "";

}
第一個注意點是介面需要加上@,也就是@interface

@Retention 可以用來修飾註解,是註解的註解,稱為元註解。有下面三種值

1、RetentionPolicy.SOURCE:註解只保留在原始檔,當Java檔案編譯成class檔案的時候,註解被遺棄;
2、RetentionPolicy.CLASS:註解被保留到class檔案,但jvm載入class檔案時候被遺棄,這是預設的生命週期;
3、RetentionPolicy.RUNTIME:註解不僅被儲存到class檔案中,jvm載入class檔案之後,仍然存在;最常用

@Target 表示當前註解可以註解在哪些內容上

ElementType列舉值註解作用的範圍
FIELD欄位上
METHOD方法上
PARAMETER方法的引數上
CONSTRUCTOR構造方法上
PACKAGE包上,參考部落格
MODULE模組上 jdk9新增
TYPE類、介面(包括註解型別)、列舉宣告、使用者自定義的註解
TYPE_USE任意使用型別的地方
TYPE_PARAMETER任何宣告型別的地方
LOCAL_VARIABLE本地變數上
ANNOTATION_TYPE元註解上
RECORD_COMPONENTrecord類上jdk14新值

二、實體類上使用註解

加上註解的User物件,暫時註解和物件沒有任何關係,只是加上了註解,不要妄想這樣註解就能用了,功能部分都要我們自己新增進去,不然鬼知道這個註解到底有什麼用途。
如果你想讓註解注入屬性值、判空等操作,那麼我們需要通過反射來處理,後面有使用反射給加上註解的欄位賦值

public class User {
    @MyAnnotation(value = "z3")
    private String name;

    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

三、給註解新增功能

通過反射獲取欄位上的註解,然後獲得註解上的值,將註解中的值注入對應欄位中

如果要做其它操作,例如判空,賦初值。則得到欄位後自己根據需求修改即可。下面只是一個測試案例。

import java.lang.reflect.Field;
import java.util.Optional;

public class MyAnnotationTest {

    public static void main(String[] args) {
        User user = new User(); // 得到一個一個物件
        Field[] fields = user.getClass().getDeclaredFields();// 反射方式得到物件的所有欄位
        for (int i = 0; i < fields.length; i++) { // 遍歷欄位陣列
            fields[i].setAccessible(true);  // 將當前欄位設定為可訪問,不然後面就會報錯
            MyAnnotation annotation = fields[i].getAnnotation(MyAnnotation.class); // 獲得這個欄位的註解
            Optional<MyAnnotation> annotationOptional = Optional.ofNullable(annotation);// 通過java8的Optional容器包裹
            if (!annotationOptional.isEmpty()) {    // 判斷註解是否存在,age屬性沒有新增註解因此需要判空
                String value = annotation.value();  // 得到註解上的值,如果沒有值則會是預設的 "" 註解的value方法的default設定
                try {
                    fields[i].set(user, value);     // 給欄位賦值
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
            fields[i].setAccessible(false);     // 重新將欄位設定為不可訪問
        }
        System.out.println(user.toString());    // 列印賦值後的user物件
    }

}

相關文章