深入理解 Java 注解
本文內容基于 JDK8。注解是 JDK5 引入的,后續 JDK 版本擴展了一些內容,本文中沒有明確指明版本的注解都是 JDK5 就已經支持的注解。
?? 本文已歸檔到:「javacore」
?? 本文中的示例代碼已歸檔到:「javacore」
1. 簡介
1.1. 注解的形式
Java 中,注解是以 @
字符開始的修飾符。如下:
@Override
void mySuperMethod() { ... }
注解可以包含命名或未命名的屬性,并且這些屬性有值。
@Author(
name = "Benjamin Franklin",
date = "3/27/2003"
)
class MyClass() { ... }
如果只有一個名為 value 的屬性,那么名稱可以省略,如:
@SuppressWarnings("unchecked")
void myMethod() { ... }
如果注解沒有屬性,則稱為標記注解
。如:@Override
。
1.2. 什么是注解
從本質上來說,注解是一種標簽,其實質上可以視為一種特殊的注釋,如果沒有解析它的代碼,它并不比普通注釋強。
解析一個注解往往有兩種形式:
- 編譯期直接的掃描 - 編譯器的掃描指的是編譯器在對 java 代碼編譯字節碼的過程中會檢測到某個類或者方法被一些注解修飾,這時它就會對于這些注解進行某些處理。這種情況只適用于 JDK 內置的注解類。
- 運行期的反射 - 如果要自定義注解,Java 編譯器無法識別并處理這個注解,它只能根據該注解的作用范圍來選擇是否編譯進字節碼文件。如果要處理注解,必須利用反射技術,識別該注解以及它所攜帶的信息,然后做相應的處理。
1.3. 注解的作用
注解有許多用途:
- 編譯器信息 - 編譯器可以使用注解來檢測錯誤或抑制警告。
- 編譯時和部署時的處理 - 程序可以處理注解信息以生成代碼,XML 文件等。
- 運行時處理 - 可以在運行時檢查某些注解并處理。
作為 Java 程序員,多多少少都曾經歷過被各種配置文件(xml、properties)支配的恐懼。過多的配置文件會使得項目難以維護。個人認為,使用注解以減少配置文件或代碼,是注解最大的用處。
1.4. 注解的代價
凡事有得必有失,注解技術同樣如此。使用注解也有一定的代價:
- 顯然,它是一種侵入式編程,那么,自然就存在著增加程序耦合度的問題。
- 自定義注解的處理需要在運行時,通過反射技術來獲取屬性。如果注解所修飾的元素是類的非 public 成員,也可以通過反射獲取。這就違背了面向對象的封裝性。
- 注解所產生的問題,相對而言,更難以 debug 或定位。
但是,正所謂瑕不掩瑜,注解所付出的代價,相較于它提供的功能而言,還是可以接受的。
1.5. 注解的應用范圍
注解可以應用于類、字段、方法和其他程序元素的聲明。
JDK8 開始,注解的應用范圍進一步擴大,以下是新的應用范圍:
類實例初始化表達式:
new @Interned MyObject();
類型轉換:
myString = (@NonNull String) str;
實現接口的聲明:
class UnmodifiableList<T> implements
@Readonly List<@Readonly T> {}
拋出異常聲明:
void monitorTemperature()
throws @Critical TemperatureException {}
2. 內置注解
JDK 中內置了以下注解:
@Override
@Deprecated
@SuppressWarnnings
-
@SafeVarargs
(JDK7 引入) -
@FunctionalInterface
(JDK8 引入)
2.1. @Override
@Override
用于表明被修飾方法覆寫了父類的方法。
如果試圖使用 @Override
標記一個實際上并沒有覆寫父類的方法時,java 編譯器會告警。
@Override
示例:
public class OverrideAnnotationDemo {
static class Person {
public String getName() {
return "getName";
}
}
static class Man extends Person {
@Override
public String getName() {
return "override getName";
}
/**
* 放開下面的注釋,編譯時會告警
*/
/*
@Override
public String getName2() {
return "override getName2";
}
*/
}
public static void main(String[] args) {
Person per = new Man();
System.out.println(per.getName());
}
}
2.2. @Deprecated
@Deprecated
用于標明被修飾的類或類成員、類方法已經廢棄、過時,不建議使用。
@Deprecated
有一定的延續性:如果我們在代碼中通過繼承或者覆蓋的方式使用了過時的類或類成員,即使子類或子方法沒有標記為 @Deprecated
,但編譯器仍然會告警。
?? 注意:
@Deprecated
這個注解類型和 javadoc 中的@deprecated
這個 tag 是有區別的:前者是 java 編譯器識別的;而后者是被 javadoc 工具所識別用來生成文檔(包含程序成員為什么已經過時、它應當如何被禁止或者替代的描述)。
@Deprecated
示例:
public class DeprecatedAnnotationDemo {
static class DeprecatedField {
@Deprecated
public static final String DEPRECATED_FIELD = "DeprecatedField";
}
static class DeprecatedMethod {
@Deprecated
public String print() {
return "DeprecatedMethod";
}
}
@Deprecated
static class DeprecatedClass {
public String print() {
return "DeprecatedClass";
}
}
public static void main(String[] args) {
System.out.println(DeprecatedField.DEPRECATED_FIELD);
DeprecatedMethod dm = new DeprecatedMethod();
System.out.println(dm.print());
DeprecatedClass dc = new DeprecatedClass();
System.out.println(dc.print());
}
}
//Output:
//DeprecatedField
//DeprecatedMethod
//DeprecatedClass
2.3. @SuppressWarnnings
@SuppressWarnings
用于關閉對類、方法、成員編譯時產生的特定警告。
@SuppressWarning
不是一個標記注解。它有一個類型為 String[]
的數組成員,這個數組中存儲的是要關閉的告警類型。對于 javac 編譯器來講,對 -Xlint
選項有效的警告名也同樣對 @SuppressWarings
有效,同時編譯器會忽略掉無法識別的警告名。
@SuppressWarning
示例:
@SuppressWarnings({"rawtypes", "unchecked"})
public class SuppressWarningsAnnotationDemo {
static class SuppressDemo<T> {
private T value;
public T getValue() {
return this.value;
}
public void setValue(T var) {
this.value = var;
}
}
@SuppressWarnings({"deprecation"})
public static void main(String[] args) {
SuppressDemo d = new SuppressDemo();
d.setValue("南京");
System.out.println("地名:" + d.getValue());
}
}
@SuppressWarnings
注解的常見參數值的簡單說明:
-
deprecation
- 使用了不贊成使用的類或方法時的警告; -
unchecked
- 執行了未檢查的轉換時的警告,例如當使用集合時沒有用泛型 (Generics) 來指定集合保存的類型; -
fallthrough
- 當 Switch 程序塊直接通往下一種情況而沒有 Break 時的警告; -
path
- 在類路徑、源文件路徑等中有不存在的路徑時的警告; -
serial
- 當在可序列化的類上缺少 serialVersionUID 定義時的警告; -
finally
- 任何 finally 子句不能正常完成時的警告; -
all
- 所有的警告。
@SuppressWarnings({"uncheck", "deprecation"})
public class InternalAnnotationDemo {
/**
* @SuppressWarnings 標記消除當前類的告警信息
*/
@SuppressWarnings({"deprecation"})
static class A {
public void method1() {
System.out.println("call method1");
}
/**
* @Deprecated 標記當前方法為廢棄方法,不建議使用
*/
@Deprecated
public void method2() {
System.out.println("call method2");
}
}
/**
* @Deprecated 標記當前類為廢棄類,不建議使用
*/
@Deprecated
static class B extends A {
/**
* @Override 標記顯示指明當前方法覆寫了父類或接口的方法
*/
@Override
public void method1() { }
}
public static void main(String[] args) {
A obj = new B();
obj.method1();
obj.method2();
}
}
2.4. @SafeVarargs
@SafeVarargs
在 JDK7 中引入。
@SafeVarargs
的作用是:告訴編譯器,在可變長參數中的泛型是類型安全的。可變長參數是使用數組存儲的,而數組和泛型不能很好的混合使用。
簡單的說,數組元素的數據類型在編譯和運行時都是確定的,而泛型的數據類型只有在運行時才能確定下來。因此,當把一個泛型存儲到數組中時,編譯器在編譯階段無法確認數據類型是否匹配,因此會給出警告信息;即如果泛型的真實數據類型無法和參數數組的類型匹配,會導致 ClassCastException
異常。
@SafeVarargs
注解使用范圍:
-
@SafeVarargs
注解可以用于構造方法。 -
@SafeVarargs
注解可以用于static
或final
方法。
@SafeVarargs
示例:
public class SafeVarargsAnnotationDemo {
/**
* 此方法實際上并不安全,不使用此注解,編譯時會告警
*/
@SafeVarargs
static void wrongMethod(List<String>... stringLists) {
Object[] array = stringLists;
List<Integer> tmpList = Arrays.asList(42);
array[0] = tmpList; // 語法錯誤,但是編譯不告警
String s = stringLists[0].get(0); // 運行時報 ClassCastException
}
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
List<String> list2 = new ArrayList<>();
list.add("1");
list.add("2");
wrongMethod(list, list2);
}
}
以上代碼,如果不使用 @SafeVarargs
,編譯時會告警
[WARNING] /D:/Codes/ZP/Java/javacore/codes/basics/src/main/java/io/github/dunwu/javacore/annotation/SafeVarargsAnnotationDemo.java: 某些輸入文件使用了未經檢查或不安全的操作。
[WARNING] /D:/Codes/ZP/Java/javacore/codes/basics/src/main/java/io/github/dunwu/javacore/annotation/SafeVarargsAnnotationDemo.java: 有關詳細信息, 請使用 -Xlint:unchecked 重新編譯。
2.5. @FunctionalInterface
@FunctionalInterface
在 JDK8 引入。
@FunctionalInterface
用于指示被修飾的接口是函數式接口。
需要注意的是,如果一個接口符合"函數式接口"定義,不加 @FunctionalInterface
也沒關系;但如果編寫的不是函數式接口,卻使用 @FunctionInterface
,那么編譯器會報錯。
什么是函數式接口?
函數式接口(Functional Interface)就是一個有且僅有一個抽象方法,但是可以有多個非抽象方法的接口。函數式接口可以被隱式轉換為 lambda 表達式。
函數式接口的特點:
- 接口有且只能有個一個抽象方法(抽象方法只有方法定義,沒有方法體)。
- 不能在接口中覆寫 Object 類中的 public 方法(寫了編譯器也會報錯)。
- 允許有 default 實現方法。
示例:
public class FunctionalInterfaceAnnotationDemo {
@FunctionalInterface
public interface Func1<T> {
void printMessage(T message);
}
/**
* @FunctionalInterface 修飾的接口中定義兩個抽象方法,編譯時會報錯
* @param <T>
*/
/*@FunctionalInterface
public interface Func2<T> {
void printMessage(T message);
void printMessage2(T message);
}*/
public static void main(String[] args) {
Func1 func1 = message -> System.out.println(message);
func1.printMessage("Hello");
func1.printMessage(100);
}
}
3. 元注解
JDK 中雖然內置了幾個注解,但這遠遠不能滿足開發過程中遇到的千變萬化的需求。所以我們需要自定義注解,而這就需要用到元注解。
元注解的作用就是用于定義其它的注解。
Java 中提供了以下元注解類型:
@Retention
@Target
@Documented
-
@Inherited
(JDK8 引入) -
@Repeatable
(JDK8 引入)
這些類型和它們所支持的類在 java.lang.annotation
包中可以找到。下面我們看一下每個元注解的作用和相應分參數的使用說明。
3.1. @Retention
@Retention
指明了注解的保留級別。
@Retention
源碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
RetentionPolicy
是一個枚舉類型,它定義了被 @Retention
修飾的注解所支持的保留級別:
-
RetentionPolicy.SOURCE
- 標記的注解僅在源文件中有效,編譯器會忽略。 -
RetentionPolicy.CLASS
- 標記的注解在 class 文件中有效,JVM 會忽略。 -
RetentionPolicy.RUNTIME
- 標記的注解在運行時有效。
@Retention
示例:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
public String name() default "fieldName";
public String setFuncName() default "setField";
public String getFuncName() default "getField";
public boolean defaultDBValue() default false;
}
3.2. @Documented
@Documented
表示無論何時使用指定的注解,都應使用 Javadoc(默認情況下,注釋不包含在 Javadoc 中)。更多內容可以參考:Javadoc tools page。
@Documented
示例:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Column {
public String name() default "fieldName";
public String setFuncName() default "setField";
public String getFuncName() default "getField";
public boolean defaultDBValue() default false;
}
3.3. @Target
@Target
指定注解可以修飾的元素類型。
@Target
源碼:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
ElementType
是一個枚舉類型,它定義了被 @Target
修飾的注解可以應用的范圍:
-
ElementType.ANNOTATION_TYPE
- 標記的注解可以應用于注解類型。 -
ElementType.CONSTRUCTOR
- 標記的注解可以應用于構造函數。 -
ElementType.FIELD
- 標記的注解可以應用于字段或屬性。 -
ElementType.LOCAL_VARIABLE
- 標記的注解可以應用于局部變量。 -
ElementType.METHOD
- 標記的注解可以應用于方法。 -
ElementType.PACKAGE
- 標記的注解可以應用于包聲明。 -
ElementType.PARAMETER
- 標記的注解可以應用于方法的參數。 -
ElementType.TYPE
- 標記的注解可以應用于類的任何元素。
@Target
示例:
@Target(ElementType.TYPE)
public @interface Table {
/**
* 數據表名稱注解,默認值為類名稱
* @return
*/
public String tableName() default "className";
}
@Target(ElementType.FIELD)
public @interface NoDBColumn {}
3.4. @Inherited
@Inherited
表示注解類型可以被繼承(默認情況下不是這樣)。
表示自動繼承注解類型。 如果注解類型聲明中存在 @Inherited
元注解,則注解所修飾類的所有子類都將會繼承此注解。
?? 注意:
@Inherited
注解類型是被標注過的類的子類所繼承。類并不從它所實現的接口繼承注解,方法并不從它所覆寫的方法繼承注解。此外,當
@Inherited
類型標注的注解的@Retention
是RetentionPolicy.RUNTIME
,則反射 API 增強了這種繼承性。如果我們使用java.lang.reflect
去查詢一個@Inherited
類型的注解時,反射代碼檢查將展開工作:檢查類和其父類,直到發現指定的注解類型被發現,或者到達類繼承結構的頂層。
@Inherited
public @interface Greeting {
public enum FontColor{ BULE,RED,GREEN};
String name();
FontColor fontColor() default FontColor.GREEN;
}
3.5. @Repeatable
@Repeatable
表示注解可以重復使用。
以 Spring @Scheduled
為例:
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Schedules {
Scheduled[] value();
}
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
// ...
}
應用示例:
public class TaskRunner {
@Scheduled("0 0/15 * * * ?")
@Scheduled("0 0 12 * ?")
public void task1() {}
}
4. 自定義注解
使用 @interface
自定義注解時,自動繼承了 java.lang.annotation.Annotation
接口,由編譯程序自動完成其他細節。在定義注解時,不能繼承其他的注解或接口。@interface
用來聲明一個注解,其中的每一個方法實際上是聲明了一個配置參數。方法的名稱就是參數的名稱,返回值類型就是參數的類型(返回值類型只能是基本類型、Class、String、enum)。可以通過 default
來聲明參數的默認值。
這里,我會通過實現一個名為 RegexValid
的正則校驗注解工具來展示自定義注解的全步驟。
4.1. 注解的定義
注解的語法格式如下:
public @interface 注解名 {定義體}
我們來定義一個注解:
@Documented
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface RegexValid {}
說明:
通過上一節對于元注解
@Target
、@Retention
、@Documented
的說明,這里就很容易理解了。
- 上面的代碼中定義了一個名為
@RegexValid
的注解。@Documented
表示@RegexValid
應該使用 javadoc。@Target({ElementType.FIELD, ElementType.PARAMETER})
表示@RegexValid
可以在類成員或方法參數上修飾。- @Retention(RetentionPolicy.RUNTIME) 表示
@RegexValid
在運行時有效。
此時,我們已經定義了一個沒有任何屬性的注解,如果到此為止,它僅僅是一個標記注解。作為正則工具,沒有屬性可什么也做不了。接下來,我們將為它添加注解屬性。
4.2. 注解屬性
注解屬性的語法形式如下:
[訪問級別修飾符] [數據類型] 名稱() default 默認值;
例如,我們要定義在注解中定義一個名為 value 的字符串屬性,其默認值為空字符串,訪問級別為默認級別,那么應該定義如下:
String value() default "";
?? 注意:在注解中,我們定義屬性時,屬性名后面需要加
()
。
定義注解屬性有以下要點:
注解屬性只能使用
public
或默認訪問級別(即不指定訪問級別修飾符)修飾。-
注解屬性的數據類型有限制要求。支持的數據類型如下:
- 所有基本數據類型(byte、char、short、int、long、float、double、boolean)
- String 類型
- Class 類
- enum 類型
- Annotation 類型
- 以上所有類型的數組
注解屬性必須有確定的值,建議指定默認值。注解屬性只能通過指定默認值或使用注解時指定屬性值,相較之下,指定默認值的方式更為可靠。注解屬性如果是引用類型,不可以為 null。這個約束使得注解處理器很難判斷注解屬性是默認值,或是使用注解時所指定的屬性值。為此,我們設置默認值時,一般會定義一些特殊的值,例如空字符串或者負數。
如果注解中只有一個屬性值,最好將其命名為 value。因為,指定屬性名為 value,在使用注解時,指定 value 的值可以不指定屬性名稱。
// 這兩種方式效果相同
@RegexValid("^((\\+)?86\\s*)?((13[0-9])|(15([0-3]|[5-9]))|(18[0,2,5-9]))\\d{8}$")
@RegexValid(value = "^((\\+)?86\\s*)?((13[0-9])|(15([0-3]|[5-9]))|(18[0,2,5-9]))\\d{8}$")
示例:
了解了注解屬性的定義要點,讓我們來為 @RegexValid
注解定義幾個屬性。
@Documented
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface RegexValid {
enum Policy {
// @formatter:off
EMPTY(null),
DATE("^(?:(?!0000)[0-9]{4}([-/.]?)(?:(?:0?[1-9]|1[0-2])\\1(?:0?[1-9]|1[0-9]|2[0-8])|(?:0?[13-9]|1[0-2])\\1"
+ "(?:29|30)|(?:0?[13578]|1[02])\\1(?:31))|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|"
+ "(?:0[48]|[2468][048]|[13579][26])00)([-/.]?)0?2\\2(?:29))$"),
MAIL("^[A-Za-z0-9](([_\\.\\-]?[a-zA-Z0-9]+)*)@([A-Za-z0-9]+)(([\\.\\-]?[a-zA-Z0-9]+)*)\\.([A-Za-z]{2,})$");
// @formatter:on
private String policy;
Policy(String policy) {
this.policy = policy;
}
public String getPolicy() {
return policy;
}
}
String value() default "";
Policy policy() default Policy.EMPTY;
}
說明:
在上面的示例代碼中,我們定義了兩個注解屬性:
String
類型的 value 屬性和Policy
枚舉類型的 policy 屬性。Policy
枚舉中定義了幾個默認的正則表達式,這是為了直接使用這幾個常用表達式去正則校驗。考慮到,我們可能需要自己傳入一些自定義正則表達式去校驗其他場景,所以定義了 value 屬性,允許使用者傳入正則表達式。
至此,@RegexValid
的聲明已經結束。但是,程序仍不知道如何處理 @RegexValid
這個注解。我們還需要定義注解處理器。
4.3. 注解處理器
如果沒有用來讀取注解的方法和工作,那么注解也就不會比注釋更有用處了。使用注解的過程中,很重要的一部分就是創建于使用注解處理器。JDK5 擴展了反射機制的 API,以幫助程序員快速的構造自定義注解處理器。
java.lang.annotation.Annotation
是一個接口,程序可以通過反射來獲取指定程序元素的注解對象,然后通過注解對象來獲取注解里面的元數據。
Annotation
接口源碼如下:
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
除此之外,Java 中支持注解處理器接口 java.lang.reflect.AnnotatedElement
,該接口代表程序中可以接受注解的程序元素,該接口主要有如下幾個實現類:
-
Class
- 類定義 -
Constructor
- 構造器定義 -
Field
- 累的成員變量定義 -
Method
- 類的方法定義 -
Package
- 類的包定義
java.lang.reflect
包下主要包含一些實現反射功能的工具類。實際上,java.lang.reflect
包所有提供的反射 API 擴充了讀取運行時注解信息的能力。當一個注解類型被定義為運行時的注解后,該注解才能是運行時可見,當 class 文件被裝載時被保存在 class 文件中的注解才會被虛擬機讀取。
AnnotatedElement
接口是所有程序元素(Class、Method 和 Constructor)的父接口,所以程序通過反射獲取了某個類的AnnotatedElement
對象之后,程序就可以調用該對象的如下四個個方法來訪問注解信息:
-
getAnnotation
- 返回該程序元素上存在的、指定類型的注解,如果該類型注解不存在,則返回 null。 -
getAnnotations
- 返回該程序元素上存在的所有注解。 -
isAnnotationPresent
- 判斷該程序元素上是否包含指定類型的注解,存在則返回 true,否則返回 false。 -
getDeclaredAnnotations
- 返回直接存在于此元素上的所有注釋。與此接口中的其他方法不同,該方法將忽略繼承的注釋。(如果沒有注釋直接存在于此元素上,則返回長度為零的一個數組。)該方法的調用者可以隨意修改返回的數組;這不會對其他調用者返回的數組產生任何影響。
了解了以上內容,讓我們來實現 @RegexValid
的注解處理器:
import java.lang.reflect.Field;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexValidUtil {
public static boolean check(Object obj) throws Exception {
boolean result = true;
StringBuilder sb = new StringBuilder();
Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
// 判斷成員是否被 @RegexValid 注解所修飾
if (field.isAnnotationPresent(RegexValid.class)) {
RegexValid valid = field.getAnnotation(RegexValid.class);
// 如果 value 為空字符串,說明沒有注入自定義正則表達式,改用 policy 屬性
String value = valid.value();
if ("".equals(value)) {
RegexValid.Policy policy = valid.policy();
value = policy.getPolicy();
}
// 通過設置 setAccessible(true) 來訪問私有成員
field.setAccessible(true);
Object fieldObj = null;
try {
fieldObj = field.get(obj);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if (fieldObj == null) {
sb.append("\n")
.append(String.format("%s 類中的 %s 字段不能為空!", obj.getClass().getName(), field.getName()));
result = false;
} else {
if (fieldObj instanceof String) {
String text = (String) fieldObj;
Pattern p = Pattern.compile(value);
Matcher m = p.matcher(text);
result = m.matches();
if (!result) {
sb.append("\n").append(String.format("%s 不是合法的 %s !", text, field.getName()));
}
} else {
sb.append("\n").append(
String.format("%s 類中的 %s 字段不是字符串類型,不能使用此注解校驗!", obj.getClass().getName(), field.getName()));
result = false;
}
}
}
}
if (sb.length() > 0) {
throw new Exception(sb.toString());
}
return result;
}
}
說明:
以上示例中的注解處理器,執行步驟如下:
- 通過 getDeclaredFields 反射方法獲取傳入對象的所有成員。
- 遍歷成員,使用 isAnnotationPresent 判斷成員是否被指定注解所修飾,如果不是,直接跳過。
- 如果成員被注解所修飾,通過
RegexValid valid = field.getAnnotation(RegexValid.class);
這樣的形式獲取,注解實例化對象,然后,就可以使用valid.value()
或valid.policy()
這樣的形式獲取注解中設定的屬性值。- 根據屬性值,進行邏輯處理。
4.4. 使用注解
完成了以上工作,我們就可以使用自定義注解了,示例如下:
public class RegexValidDemo {
static class User {
private String name;
@RegexValid(policy = RegexValid.Policy.DATE)
private String date;
@RegexValid(policy = RegexValid.Policy.MAIL)
private String mail;
@RegexValid("^((\\+)?86\\s*)?((13[0-9])|(15([0-3]|[5-9]))|(18[0,2,5-9]))\\d{8}$")
private String phone;
public User(String name, String date, String mail, String phone) {
this.name = name;
this.date = date;
this.mail = mail;
this.phone = phone;
}
@Override
public String toString() {
return "User{" + "name='" + name + '\'' + ", date='" + date + '\'' + ", mail='" + mail + '\'' + ", phone='"
+ phone + '\'' + '}';
}
}
static void printDate(@RegexValid(policy = RegexValid.Policy.DATE) String date){
System.out.println(date);
}
public static void main(String[] args) throws Exception {
User user = new User("Tom", "1990-01-31", "xxx@163.com", "18612341234");
User user2 = new User("Jack", "2019-02-29", "sadhgs", "183xxxxxxxx");
if (RegexValidUtil.check(user)) {
System.out.println(user + "正則校驗通過");
}
if (RegexValidUtil.check(user2)) {
System.out.println(user2 + "正則校驗通過");
}
}
}