1. 程式人生 > 實用技巧 >Java註解的使用

Java註解的使用

Java註解的使用

參考

廖雪峰java教程

使用註解

什麼是註解(Annotation)?註解是放在Java原始碼的類、方法、欄位、引數前的一種特殊“註釋”:

註解的作用

從JVM的角度看,註解本身對程式碼邏輯沒有任何影響,如何使用註解完全由工具決定。

Java的註解可以分為三類:

第一類是由編譯器使用的註解,例如:

  • @Override:讓編譯器檢查該方法是否正確地實現了覆寫;
  • @SuppressWarnings:告訴編譯器忽略此處程式碼產生的警告。

這類註解不會被編譯進入.class檔案,它們在編譯後就被編譯器扔掉了。

第二類是由工具處理.class檔案使用的註解,比如有些工具會在載入class的時候,對class做動態修改,實現一些特殊的功能。這類註解會被編譯進入.class檔案,但載入結束後並不會存在於記憶體中。這類註解只被一些底層庫使用,一般我們不必自己處理。

第三類是在程式執行期能夠讀取的註解,它們在載入後一直存在於JVM中,這也是最常用的註解。例如,一個配置了@PostConstruct的方法會在呼叫構造方法後自動被呼叫(這是Java程式碼讀取該註解實現的功能,JVM並不會識別該註解)。

定義一個註解時,還可以定義配置引數。配置引數可以包括:

  • 所有基本型別;
  • String;
  • 列舉型別;
  • 基本型別、String、Class以及列舉的陣列。

因為配置引數必須是常量,所以,上述限制保證了註解在定義時就已經確定了每個引數的值。

註解的配置引數可以有預設值,缺少某個配置引數時將使用預設值。

此外,大部分註解會有一個名為value的配置引數,對此引數賦值,可以只寫常量,相當於省略了value引數。

如果只寫註解,相當於全部使用預設值。

小結

註解(Annotation)是Java語言用於工具處理的標註:

註解可以配置引數,沒有指定配置的引數使用預設值;

如果引數名稱是value,且只有一個引數,那麼可以省略引數名稱。

定義註解

Java語言使用@interface語法來定義註解(Annotation),它的格式如下:

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Logger {

    String value() default "";

    String level() default "error";

    int type() default 0;
}

註解的引數類似無引數方法,可以用default設定一個預設值(強烈推薦)。最常用的引數應當命名為value。

元註解

有一些註解可以修飾其他註解,這些註解就稱為元註解(meta annotation)。Java標準庫已經定義了一些元註解,我們只需要使用元註解,通常不需要自己去編寫元註解。

@Target

實際上@Target定義的value是ElementType[]陣列,只有一個元素時,可以省略陣列的寫法

最常用的元註解是@Target。使用@Target可以定義Annotation能夠被應用於原始碼的哪些位置:

  • 類或介面:ElementType.TYPE;
  • 欄位:ElementType.FIELD;
  • 方法:ElementType.METHOD;
  • 構造方法:ElementType.CONSTRUCTOR;
  • 方法引數:ElementType.PARAMETER。

@Retention

另一個重要的元註解@Retention定義了Annotation的生命週期:

  • 僅編譯期:RetentionPolicy.SOURCE;
  • 僅class檔案:RetentionPolicy.CLASS;
  • 執行期:RetentionPolicy.RUNTIME。

如果@Retention不存在,則該Annotation預設為CLASS。因為通常我們自定義的Annotation都是RUNTIME,所以,務必要加上@Retention(RetentionPolicy.RUNTIME)這個元註解

@Repeatable

使用@Repeatable這個元註解可以定義Annotation是否可重複。這個註解應用不是特別廣泛。

@Repeatable(Loggers.class)
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Logger {

    String value() default "";

    String level() default "error";

    int type() default 0;
}


@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface Loggers {
    Logger[] value();
}

經過@Repeatable修飾後,在某個型別宣告處,就可以新增多個@Report註解:

@Logger(type=1, level="debug")
@Logger(type=2, level="warning")
public class Hello {
}

@Inherited——註解繼承,作用於類上

使用@Inherited定義子類是否可繼承父類定義的Annotation。@Inherited僅針對@Target(ElementType.TYPE)型別的annotation有效,並且僅針對class的繼承,對interface的繼承無效。

@Inherited
@Repeatable(Loggers.class)
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Logger {

    String value() default "";

    String level() default "error";

    int type() default 0;
}

在使用的時候,如果一個類用到了@Logger:

@Logger(type=1)
public class Person {
}

則它的子類預設也定義了該註解:

public class Student extends Person {
}

如何定義Annotation

我們總結一下定義Annotation的步驟:

第一步,用@interface定義註解。

第二步,新增引數、預設值。把最常用的引數定義為value(),推薦所有引數都儘量設定預設值。

第三步,用元註解配置註解。其中,必須設定@Target和@Retention,@Retention一般設定為RUNTIME,因為我們自定義的註解通常要求在執行期讀取。一般情況下,不必寫@Inherited和@Repeatable。

小結

Java使用@interface定義註解:

可定義多個引數和預設值,核心引數使用value名稱;

必須設定@Target來指定Annotation可以應用的範圍;

應當設定@Retention(RetentionPolicy.RUNTIME)便於執行期讀取該Annotation。

處理註解

Java的註解本身對程式碼邏輯沒有任何影響。根據@Retention的配置:

  • SOURCE型別的註解在編譯期就被丟掉了;
  • CLASS型別的註解僅儲存在class檔案中,它們不會被載入進JVM;
  • RUNTIME型別的註解會被載入進JVM,並且在執行期可以被程式讀取。

如何使用註解完全由工具決定。SOURCE型別的註解主要由編譯器使用,因此我們一般只使用,不編寫。CLASS型別的註解主要由底層工具庫使用,涉及到class的載入,一般我們很少用到。只有RUNTIME型別的註解不但要使用,還經常需要編寫。

因此,我們只討論如何讀取RUNTIME型別的註解。

因為註解定義後也是一種class,所有的註解都繼承自java.lang.annotation.Annotation,因此,讀取註解,需要使用反射API。

Java提供的使用反射API讀取Annotation的方法包括:

判斷某個註解是否存在於Class、Field、Method或Constructor:

  • Class.isAnnotationPresent(Class)
  • Field.isAnnotationPresent(Class)
  • Method.isAnnotationPresent(Class)
  • Constructor.isAnnotationPresent(Class)

讀取方法、欄位和構造方法的Annotation和Class類似。但要讀取方法引數的Annotation就比較麻煩一點,因為方法引數本身可以看成一個數組,而每個引數又可以定義多個註解,所以,一次獲取方法引數的所有註解就必須用一個二維陣列來表示。

示例:

public class AnnotationTest {

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException {
        UserController userController = new UserController();
        boolean present = userController.getClass().isAnnotationPresent(Logger.class);
        if (present) {
            Logger logger = userController.getClass().getAnnotation(Logger.class);
            System.out.println(" logger value: " + logger.value());
            System.out.println(" logger level: " + logger.level());
        }

        Method loadUsers = UserController.class.getMethod("loadUsers");
        Logger loadUsersAnnotation = loadUsers.getAnnotation(Logger.class);
        if (loadUsersAnnotation != null) {
            System.out.println("loadUsers logger value: " + loadUsersAnnotation.value());
            System.out.println("loadUsers logger level: " + loadUsersAnnotation.level());
        }

        Method getUser = UserController.class.getMethod("getUser", String.class);
        Annotation[][] annotations = getUser.getParameterAnnotations();
        for (Annotation[] annotationArr : annotations) {
            for (Annotation annotation : annotationArr) {
                if (annotation instanceof NotEmpty) {
                    NotEmpty r = (NotEmpty) annotation;
                    System.out.println("NotEmpty");
                }
                if (annotation instanceof RequestParam) {
                    RequestParam r = (RequestParam) annotation;
                    System.out.println("RequestParam");
                }
            }
        }

        User user = new User();
        //user.setName("牛掰");
        user.setName("");
        boolean checkEmpty = AnnotationTest.checkEmpty(user);
        if (checkEmpty) {
            System.out.println("一切正常!");
        }

    }

    public static boolean checkEmpty(User user) throws IllegalAccessException {
        Field[] declaredFields = user.getClass().getDeclaredFields();
        if (declaredFields != null) {
            // 遍歷所有Field:
            for (Field field : declaredFields) {
                field.setAccessible(true);
                // 獲取Field定義的@Range:
                NotEmpty range = field.getAnnotation(NotEmpty.class);
                // 如果@Range存在:
                if (range != null) {
                    // 獲取Field的值:
                    Object value = field.get(user);
                    // 如果值是String:
                    if (value == null) {
                        if (value instanceof String) {
                            String s = (String) value;
                            if (StringUtils.isEmpty(s)) {
                                throw new IllegalArgumentException("Invalid field: " + field.getName());
                            }
                        }
                    }else {
                        throw new IllegalArgumentException("Invalid field: " + field.getName());
                    }
                }
            }
        }
        return true;
    }
}

輸出:

 logger value: UserControllerLog
 logger level: debug
loadUsers logger value: UserControllerLog.loadUsers
loadUsers logger level: info
NotEmpty
RequestParam
Exception in thread "main" java.lang.IllegalArgumentException: Invalid field: name
	at com.self.annotation.AnnotationTest.checkEmpty(AnnotationTest.java:89)
	at com.self.annotation.AnnotationTest.main(AnnotationTest.java:61)