Java注解的使用
參考
廖雪峰java教程
使用注解
什么是注解(Annotation)?注解是放在Java源碼的類、方法、字段、參數(shù)前的一種特殊“注釋”:
注解的作用
從JVM的角度看,注解本身對代碼邏輯沒有任何影響,如何使用注解完全由工具決定。
Java的注解可以分為三類:
第一類是由編譯器使用的注解,例如:
@Override:讓編譯器檢查該方法是否正確地實現(xiàn)了覆寫;
@SuppressWarnings:告訴編譯器忽略此處代碼產(chǎn)生的警告。
這類注解不會被編譯進(jìn)入.class文件,它們在編譯后就被編譯器扔掉了。
第二類是由工具處理.class文件使用的注解,比如有些工具會在加載class的時候,對class做動態(tài)修改,實現(xiàn)一些特殊的功能。這類注解會被編譯進(jìn)入.class文件,但加載結(jié)束后并不會存在于內(nèi)存中。這類注解只被一些底層庫使用,一般我們不必自己處理。
第三類是在程序運行期能夠讀取的注解,它們在加載后一直存在于JVM中,這也是最常用的注解。例如,一個配置了@PostConstruct的方法會在調(diào)用構(gòu)造方法后自動被調(diào)用(這是Java代碼讀取該注解實現(xiàn)的功能,JVM并不會識別該注解)。
定義一個注解時,還可以定義配置參數(shù)。配置參數(shù)可以包括:
所有基本類型;
String;
枚舉類型;
基本類型、String、Class以及枚舉的數(shù)組。
因為配置參數(shù)必須是常量,所以,上述保證了注解在定義時就已經(jīng)確定了每個參數(shù)的值。
注解的配置參數(shù)可以有默認(rèn)值,缺少某個配置參數(shù)時將使用默認(rèn)值。
此外,大部分注解會有一個名為value的配置參數(shù),對此參數(shù)賦值,可以只寫常量,相當(dāng)于省略了value參數(shù)。
如果只寫注解,相當(dāng)于全部使用默認(rèn)值。
小結(jié)
注解(Annotation)是Java語言用于工具處理的標(biāo)注:
注解可以配置參數(shù),沒有指定配置的參數(shù)使用默認(rèn)值;
如果參數(shù)名稱是value,且只有一個參數(shù),那么可以省略參數(shù)名稱。
定義注解
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;
}
注解的參數(shù)類似無參數(shù)方法,可以用default設(shè)定一個默認(rèn)值(強烈推薦)。最常用的參數(shù)應(yīng)當(dāng)命名為value。
元注解
有一些注解可以修飾其他注解,這些注解就稱為元注解(meta annotation)。Java標(biāo)準(zhǔn)庫已經(jīng)定義了一些元注解,我們只需要使用元注解,通常不需要自己去編寫元注解。
@Target
實際上@Target定義的value是ElementType[]數(shù)組,只有一個元素時,可以省略數(shù)組的寫法
最常用的元注解是@Target。使用@Target可以定義Annotation能夠被應(yīng)用于源碼的哪些位置:
類或接口:ElementType.TYPE;
字段:ElementType.FIELD;
方法:ElementType.METHOD;
構(gòu)造方法:ElementType.CONSTRUCTOR;
方法參數(shù):ElementType.PARAMETER。
@Retention
另一個重要的元注解@Retention定義了Annotation的生命周期:
僅編譯期:RetentionPolicy.SOURCE;
僅class文件:RetentionPolicy.CLASS;
運行期:RetentionPolicy.RUNTIME。
如果@Retention不存在,則該Annotation默認(rèn)為CLASS。因為通常我們自定義的Annotation都是RUNTIME,所以,務(wù)必要加上@Retention(RetentionPolicy.RUNTIME)這個元注解
@Repeatable
使用@Repeatable這個元注解可以定義Annotation是否可重復(fù)。這個注解應(yīng)用不是特別廣泛。
@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();
}
經(jīng)過@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 {
}
則它的子類默認(rèn)也定義了該注解:
public class Student extends Person {
}
如何定義Annotation
我們總結(jié)一下定義Annotation的步驟:
第一步,用@interface定義注解。
第二步,添加參數(shù)、默認(rèn)值。把最常用的參數(shù)定義為value(),推薦所有參數(shù)都盡量設(shè)置默認(rèn)值。
第三步,用元注解配置注解。其中,必須設(shè)置@Target和@Retention,@Retention一般設(shè)置為RUNTIME,因為我們自定義的注解通常要求在運行期讀取。一般情況下,不必寫@Inherited和@Repeatable。
小結(jié)
Java使用@interface定義注解:
可定義多個參數(shù)和默認(rèn)值,核心參數(shù)使用value名稱;
必須設(shè)置@Target來指定Annotation可以應(yīng)用的范圍;
應(yīng)當(dāng)設(shè)置@Retention(RetentionPolicy.RUNTIME)便于運行期讀取該Annotation。
處理注解
Java的注解本身對代碼邏輯沒有任何影響。根據(jù)@Retention的配置:
SOURCE類型的注解在編譯期就被丟掉了;
CLASS類型的注解僅保存在class文件中,它們不會被加載進(jìn)JVM;
RUNTIME類型的注解會被加載進(jìn)JVM,并且在運行期可以被程序讀取。
如何使用注解完全由工具決定。SOURCE類型的注解主要由編譯器使用,因此我們一般只使用,不編寫。CLASS類型的注解主要由底層工具庫使用,涉及到class的加載,一般我們很少用到。只有RUNTIME類型的注解不但要使用,還經(jīng)常需要編寫。
因此,我們只討論如何讀取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)
讀取方法、字段和構(gòu)造方法的Annotation和Class類似。但要讀取方法參數(shù)的Annotation就比較麻煩一點,因為方法參數(shù)本身可以看成一個數(shù)組,而每個參數(shù)又可以定義多個注解,所以,一次獲取方法參數(shù)的所有注解就必須用一個二維數(shù)組來表示。
示例:
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:)
at com.self.annotation.AnnotationTest.main(AnnotationTest.java:61)
因篇幅問題不能全部顯示,請點此查看更多更全內(nèi)容
Copyright ? 2019- 91gzw.com 版權(quán)所有 湘ICP備2023023988號-2
違法及侵權(quán)請聯(lián)系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com
本站由北京市萬商天勤律師事務(wù)所王興未律師提供法律服務(wù)