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)