深入理解 Java 注解

深入理解 Java 注解

本文內(nèi)容基于 JDK8。注解是 JDK5 引入的,后續(xù) JDK 版本擴(kuò)展了一些內(nèi)容,本文中沒有明確指明版本的注解都是 JDK5 就已經(jīng)支持的注解。

?? 本文已歸檔到:「javacore

?? 本文中的示例代碼已歸檔到:「javacore

1. 簡(jiǎn)介

1.1. 注解的形式

Java 中,注解是以 @ 字符開始的修飾符。如下:

@Override
void mySuperMethod() { ... }

注解可以包含命名或未命名的屬性,并且這些屬性有值。

@Author(
   name = "Benjamin Franklin",
   date = "3/27/2003"
)
class MyClass() { ... }

如果只有一個(gè)名為 value 的屬性,那么名稱可以省略,如:

@SuppressWarnings("unchecked")
void myMethod() { ... }

如果注解沒有屬性,則稱為標(biāo)記注解。如:@Override。

1.2. 什么是注解

從本質(zhì)上來說,注解是一種標(biāo)簽,其實(shí)質(zhì)上可以視為一種特殊的注釋,如果沒有解析它的代碼,它并不比普通注釋強(qiáng)。

解析一個(gè)注解往往有兩種形式:

  • 編譯期直接的掃描 - 編譯器的掃描指的是編譯器在對(duì) java 代碼編譯字節(jié)碼的過程中會(huì)檢測(cè)到某個(gè)類或者方法被一些注解修飾,這時(shí)它就會(huì)對(duì)于這些注解進(jìn)行某些處理。這種情況只適用于 JDK 內(nèi)置的注解類。
  • 運(yùn)行期的反射 - 如果要自定義注解,Java 編譯器無法識(shí)別并處理這個(gè)注解,它只能根據(jù)該注解的作用范圍來選擇是否編譯進(jìn)字節(jié)碼文件。如果要處理注解,必須利用反射技術(shù),識(shí)別該注解以及它所攜帶的信息,然后做相應(yīng)的處理。

1.3. 注解的作用

注解有許多用途:

  • 編譯器信息 - 編譯器可以使用注解來檢測(cè)錯(cuò)誤或抑制警告。
  • 編譯時(shí)和部署時(shí)的處理 - 程序可以處理注解信息以生成代碼,XML 文件等。
  • 運(yùn)行時(shí)處理 - 可以在運(yùn)行時(shí)檢查某些注解并處理。

作為 Java 程序員,多多少少都曾經(jīng)歷過被各種配置文件(xml、properties)支配的恐懼。過多的配置文件會(huì)使得項(xiàng)目難以維護(hù)。個(gè)人認(rèn)為,使用注解以減少配置文件或代碼,是注解最大的用處。

1.4. 注解的代價(jià)

凡事有得必有失,注解技術(shù)同樣如此。使用注解也有一定的代價(jià):

  • 顯然,它是一種侵入式編程,那么,自然就存在著增加程序耦合度的問題。
  • 自定義注解的處理需要在運(yùn)行時(shí),通過反射技術(shù)來獲取屬性。如果注解所修飾的元素是類的非 public 成員,也可以通過反射獲取。這就違背了面向?qū)ο蟮姆庋b性。
  • 注解所產(chǎn)生的問題,相對(duì)而言,更難以 debug 或定位。

但是,正所謂瑕不掩瑜,注解所付出的代價(jià),相較于它提供的功能而言,還是可以接受的。

1.5. 注解的應(yīng)用范圍

注解可以應(yīng)用于類、字段、方法和其他程序元素的聲明。

JDK8 開始,注解的應(yīng)用范圍進(jìn)一步擴(kuò)大,以下是新的應(yīng)用范圍:

類實(shí)例初始化表達(dá)式:

new @Interned MyObject();

類型轉(zhuǎn)換:

myString = (@NonNull String) str;

實(shí)現(xiàn)接口的聲明:

class UnmodifiableList<T> implements
    @Readonly List<@Readonly T> {}

拋出異常聲明:

void monitorTemperature()
    throws @Critical TemperatureException {}

2. 內(nèi)置注解

JDK 中內(nèi)置了以下注解:

  • @Override
  • @Deprecated
  • @SuppressWarnnings
  • @SafeVarargs(JDK7 引入)
  • @FunctionalInterface(JDK8 引入)

2.1. @Override

@Override 用于表明被修飾方法覆寫了父類的方法。

如果試圖使用 @Override 標(biāo)記一個(gè)實(shí)際上并沒有覆寫父類的方法時(shí),java 編譯器會(huì)告警。

@Override 示例:

public class OverrideAnnotationDemo {

    static class Person {
        public String getName() {
            return "getName";
        }
    }


    static class Man extends Person {
        @Override
        public String getName() {
            return "override getName";
        }

        /**
         *  放開下面的注釋,編譯時(shí)會(huì)告警
         */
       /*
        @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 用于標(biāo)明被修飾的類或類成員、類方法已經(jīng)廢棄、過時(shí),不建議使用。

@Deprecated 有一定的延續(xù)性:如果我們?cè)诖a中通過繼承或者覆蓋的方式使用了過時(shí)的類或類成員,即使子類或子方法沒有標(biāo)記為 @Deprecated,但編譯器仍然會(huì)告警。

?? 注意: @Deprecated 這個(gè)注解類型和 javadoc 中的 @deprecated 這個(gè) tag 是有區(qū)別的:前者是 java 編譯器識(shí)別的;而后者是被 javadoc 工具所識(shí)別用來生成文檔(包含程序成員為什么已經(jīng)過時(shí)、它應(yīng)當(dāng)如何被禁止或者替代的描述)。

@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 用于關(guān)閉對(duì)類、方法、成員編譯時(shí)產(chǎn)生的特定警告。

@SuppressWarning 不是一個(gè)標(biāo)記注解。它有一個(gè)類型為 String[] 的數(shù)組成員,這個(gè)數(shù)組中存儲(chǔ)的是要關(guān)閉的告警類型。對(duì)于 javac 編譯器來講,對(duì) -Xlint 選項(xiàng)有效的警告名也同樣對(duì) @SuppressWarings 有效,同時(shí)編譯器會(huì)忽略掉無法識(shí)別的警告名。

@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 注解的常見參數(shù)值的簡(jiǎn)單說明:

  • deprecation - 使用了不贊成使用的類或方法時(shí)的警告;
  • unchecked - 執(zhí)行了未檢查的轉(zhuǎn)換時(shí)的警告,例如當(dāng)使用集合時(shí)沒有用泛型 (Generics) 來指定集合保存的類型;
  • fallthrough - 當(dāng) Switch 程序塊直接通往下一種情況而沒有 Break 時(shí)的警告;
  • path - 在類路徑、源文件路徑等中有不存在的路徑時(shí)的警告;
  • serial - 當(dāng)在可序列化的類上缺少 serialVersionUID 定義時(shí)的警告;
  • finally - 任何 finally 子句不能正常完成時(shí)的警告;
  • all - 所有的警告。
@SuppressWarnings({"uncheck", "deprecation"})
public class InternalAnnotationDemo {

    /**
     * @SuppressWarnings 標(biāo)記消除當(dāng)前類的告警信息
     */
    @SuppressWarnings({"deprecation"})
    static class A {
        public void method1() {
            System.out.println("call method1");
        }

        /**
         * @Deprecated 標(biāo)記當(dāng)前方法為廢棄方法,不建議使用
         */
        @Deprecated
        public void method2() {
            System.out.println("call method2");
        }
    }

    /**
     * @Deprecated 標(biāo)記當(dāng)前類為廢棄類,不建議使用
     */
    @Deprecated
    static class B extends A {
        /**
         * @Override 標(biāo)記顯示指明當(dāng)前方法覆寫了父類或接口的方法
         */
        @Override
        public void method1() { }
    }

    public static void main(String[] args) {
        A obj = new B();
        obj.method1();
        obj.method2();
    }
}

2.4. @SafeVarargs

@SafeVarargs 在 JDK7 中引入。

@SafeVarargs 的作用是:告訴編譯器,在可變長(zhǎng)參數(shù)中的泛型是類型安全的??勺冮L(zhǎng)參數(shù)是使用數(shù)組存儲(chǔ)的,而數(shù)組和泛型不能很好的混合使用。

簡(jiǎn)單的說,數(shù)組元素的數(shù)據(jù)類型在編譯和運(yùn)行時(shí)都是確定的,而泛型的數(shù)據(jù)類型只有在運(yùn)行時(shí)才能確定下來。因此,當(dāng)把一個(gè)泛型存儲(chǔ)到數(shù)組中時(shí),編譯器在編譯階段無法確認(rèn)數(shù)據(jù)類型是否匹配,因此會(huì)給出警告信息;即如果泛型的真實(shí)數(shù)據(jù)類型無法和參數(shù)數(shù)組的類型匹配,會(huì)導(dǎo)致 ClassCastException 異常。

@SafeVarargs 注解使用范圍:

  • @SafeVarargs 注解可以用于構(gòu)造方法。
  • @SafeVarargs 注解可以用于 staticfinal 方法。

@SafeVarargs 示例:

public class SafeVarargsAnnotationDemo {
    /**
     * 此方法實(shí)際上并不安全,不使用此注解,編譯時(shí)會(huì)告警
     */
    @SafeVarargs
    static void wrongMethod(List<String>... stringLists) {
        Object[] array = stringLists;
        List<Integer> tmpList = Arrays.asList(42);
        array[0] = tmpList; // 語法錯(cuò)誤,但是編譯不告警
        String s = stringLists[0].get(0); // 運(yùn)行時(shí)報(bào) 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 ,編譯時(shí)會(huì)告警

[WARNING] /D:/Codes/ZP/Java/javacore/codes/basics/src/main/java/io/github/dunwu/javacore/annotation/SafeVarargsAnnotationDemo.java: 某些輸入文件使用了未經(jīng)檢查或不安全的操作。
[WARNING] /D:/Codes/ZP/Java/javacore/codes/basics/src/main/java/io/github/dunwu/javacore/annotation/SafeVarargsAnnotationDemo.java: 有關(guān)詳細(xì)信息, 請(qǐng)使用 -Xlint:unchecked 重新編譯。

2.5. @FunctionalInterface

@FunctionalInterface 在 JDK8 引入。

@FunctionalInterface 用于指示被修飾的接口是函數(shù)式接口。

需要注意的是,如果一個(gè)接口符合"函數(shù)式接口"定義,不加 @FunctionalInterface 也沒關(guān)系;但如果編寫的不是函數(shù)式接口,卻使用 @FunctionInterface,那么編譯器會(huì)報(bào)錯(cuò)。

什么是函數(shù)式接口?

函數(shù)式接口(Functional Interface)就是一個(gè)有且僅有一個(gè)抽象方法,但是可以有多個(gè)非抽象方法的接口。函數(shù)式接口可以被隱式轉(zhuǎn)換為 lambda 表達(dá)式。

函數(shù)式接口的特點(diǎn):

  • 接口有且只能有個(gè)一個(gè)抽象方法(抽象方法只有方法定義,沒有方法體)。
  • 不能在接口中覆寫 Object 類中的 public 方法(寫了編譯器也會(huì)報(bào)錯(cuò))。
  • 允許有 default 實(shí)現(xiàn)方法。

示例:

public class FunctionalInterfaceAnnotationDemo {

    @FunctionalInterface
    public interface Func1<T> {
        void printMessage(T message);
    }

    /**
     * @FunctionalInterface 修飾的接口中定義兩個(gè)抽象方法,編譯時(shí)會(huì)報(bào)錯(cuò)
     * @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 中雖然內(nèi)置了幾個(gè)注解,但這遠(yuǎn)遠(yuǎn)不能滿足開發(fā)過程中遇到的千變?nèi)f化的需求。所以我們需要自定義注解,而這就需要用到元注解。

元注解的作用就是用于定義其它的注解

Java 中提供了以下元注解類型:

  • @Retention
  • @Target
  • @Documented
  • @Inherited(JDK8 引入)
  • @Repeatable(JDK8 引入)

這些類型和它們所支持的類在 java.lang.annotation 包中可以找到。下面我們看一下每個(gè)元注解的作用和相應(yīng)分參數(shù)的使用說明。

3.1. @Retention

@Retention 指明了注解的保留級(jí)別。

@Retention 源碼:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}

RetentionPolicy 是一個(gè)枚舉類型,它定義了被 @Retention 修飾的注解所支持的保留級(jí)別:

  • RetentionPolicy.SOURCE - 標(biāo)記的注解僅在源文件中有效,編譯器會(huì)忽略。
  • RetentionPolicy.CLASS - 標(biāo)記的注解在 class 文件中有效,JVM 會(huì)忽略。
  • RetentionPolicy.RUNTIME - 標(biāo)記的注解在運(yùn)行時(shí)有效。

@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 表示無論何時(shí)使用指定的注解,都應(yīng)使用 Javadoc(默認(rèn)情況下,注釋不包含在 Javadoc 中)。更多內(nèi)容可以參考: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 是一個(gè)枚舉類型,它定義了被 @Target 修飾的注解可以應(yīng)用的范圍:

  • ElementType.ANNOTATION_TYPE - 標(biāo)記的注解可以應(yīng)用于注解類型。
  • ElementType.CONSTRUCTOR - 標(biāo)記的注解可以應(yīng)用于構(gòu)造函數(shù)。
  • ElementType.FIELD - 標(biāo)記的注解可以應(yīng)用于字段或?qū)傩浴?/li>
  • ElementType.LOCAL_VARIABLE - 標(biāo)記的注解可以應(yīng)用于局部變量。
  • ElementType.METHOD - 標(biāo)記的注解可以應(yīng)用于方法。
  • ElementType.PACKAGE - 標(biāo)記的注解可以應(yīng)用于包聲明。
  • ElementType.PARAMETER - 標(biāo)記的注解可以應(yīng)用于方法的參數(shù)。
  • ElementType.TYPE - 標(biāo)記的注解可以應(yīng)用于類的任何元素。

@Target 示例:

@Target(ElementType.TYPE)
public @interface Table {
    /**
     * 數(shù)據(jù)表名稱注解,默認(rèn)值為類名稱
     * @return
     */
    public String tableName() default "className";
}

@Target(ElementType.FIELD)
public @interface NoDBColumn {}

3.4. @Inherited

@Inherited 表示注解類型可以被繼承(默認(rèn)情況下不是這樣)。

表示自動(dòng)繼承注解類型。 如果注解類型聲明中存在 @Inherited 元注解,則注解所修飾類的所有子類都將會(huì)繼承此注解。

?? 注意:@Inherited 注解類型是被標(biāo)注過的類的子類所繼承。類并不從它所實(shí)現(xiàn)的接口繼承注解,方法并不從它所覆寫的方法繼承注解。

此外,當(dāng) @Inherited 類型標(biāo)注的注解的 @RetentionRetentionPolicy.RUNTIME,則反射 API 增強(qiáng)了這種繼承性。如果我們使用 java.lang.reflect 去查詢一個(gè) @Inherited 類型的注解時(shí),反射代碼檢查將展開工作:檢查類和其父類,直到發(fā)現(xiàn)指定的注解類型被發(fā)現(xiàn),或者到達(dá)類繼承結(jié)構(gòu)的頂層。

@Inherited
public @interface Greeting {
    public enum FontColor{ BULE,RED,GREEN};
    String name();
    FontColor fontColor() default FontColor.GREEN;
}

3.5. @Repeatable

@Repeatable 表示注解可以重復(fù)使用。

以 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 {
  // ...
}

應(yīng)用示例:

public class TaskRunner {

    @Scheduled("0 0/15 * * * ?")
    @Scheduled("0 0 12 * ?")
    public void task1() {}
}

4. 自定義注解

使用 @interface 自定義注解時(shí),自動(dòng)繼承了 java.lang.annotation.Annotation 接口,由編譯程序自動(dòng)完成其他細(xì)節(jié)。在定義注解時(shí),不能繼承其他的注解或接口。@interface 用來聲明一個(gè)注解,其中的每一個(gè)方法實(shí)際上是聲明了一個(gè)配置參數(shù)。方法的名稱就是參數(shù)的名稱,返回值類型就是參數(shù)的類型(返回值類型只能是基本類型、Class、String、enum)。可以通過 default 來聲明參數(shù)的默認(rèn)值。

這里,我會(huì)通過實(shí)現(xiàn)一個(gè)名為 RegexValid 的正則校驗(yàn)注解工具來展示自定義注解的全步驟。

4.1. 注解的定義

注解的語法格式如下:

public @interface 注解名 {定義體}

我們來定義一個(gè)注解:

@Documented
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface RegexValid {}

說明:

通過上一節(jié)對(duì)于元注解 @Target、@Retention、@Documented 的說明,這里就很容易理解了。

  • 上面的代碼中定義了一個(gè)名為 @RegexValid 的注解。
  • @Documented 表示 @RegexValid 應(yīng)該使用 javadoc。
  • @Target({ElementType.FIELD, ElementType.PARAMETER}) 表示 @RegexValid 可以在類成員或方法參數(shù)上修飾。
  • @Retention(RetentionPolicy.RUNTIME) 表示 @RegexValid 在運(yùn)行時(shí)有效。

此時(shí),我們已經(jīng)定義了一個(gè)沒有任何屬性的注解,如果到此為止,它僅僅是一個(gè)標(biāo)記注解。作為正則工具,沒有屬性可什么也做不了。接下來,我們將為它添加注解屬性。

4.2. 注解屬性

注解屬性的語法形式如下:

[訪問級(jí)別修飾符] [數(shù)據(jù)類型] 名稱() default 默認(rèn)值;

例如,我們要定義在注解中定義一個(gè)名為 value 的字符串屬性,其默認(rèn)值為空字符串,訪問級(jí)別為默認(rèn)級(jí)別,那么應(yīng)該定義如下:

String value() default "";

?? 注意:在注解中,我們定義屬性時(shí),屬性名后面需要加 ()

定義注解屬性有以下要點(diǎn):

  • 注解屬性只能使用 public 或默認(rèn)訪問級(jí)別(即不指定訪問級(jí)別修飾符)修飾。

  • 注解屬性的數(shù)據(jù)類型有限制要求。支持的數(shù)據(jù)類型如下:

    • 所有基本數(shù)據(jù)類型(byte、char、short、int、long、float、double、boolean)
    • String 類型
    • Class 類
    • enum 類型
    • Annotation 類型
    • 以上所有類型的數(shù)組
  • 注解屬性必須有確定的值,建議指定默認(rèn)值。注解屬性只能通過指定默認(rèn)值或使用注解時(shí)指定屬性值,相較之下,指定默認(rèn)值的方式更為可靠。注解屬性如果是引用類型,不可以為 null。這個(gè)約束使得注解處理器很難判斷注解屬性是默認(rèn)值,或是使用注解時(shí)所指定的屬性值。為此,我們?cè)O(shè)置默認(rèn)值時(shí),一般會(huì)定義一些特殊的值,例如空字符串或者負(fù)數(shù)。

  • 如果注解中只有一個(gè)屬性值,最好將其命名為 value。因?yàn)?,指定屬性名?value,在使用注解時(shí),指定 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}$")

示例:

了解了注解屬性的定義要點(diǎn),讓我們來為 @RegexValid 注解定義幾個(gè)屬性。

@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;
}

說明:

在上面的示例代碼中,我們定義了兩個(gè)注解屬性:String 類型的 value 屬性和 Policy 枚舉類型的 policy 屬性。Policy 枚舉中定義了幾個(gè)默認(rèn)的正則表達(dá)式,這是為了直接使用這幾個(gè)常用表達(dá)式去正則校驗(yàn)??紤]到,我們可能需要自己傳入一些自定義正則表達(dá)式去校驗(yàn)其他場(chǎng)景,所以定義了 value 屬性,允許使用者傳入正則表達(dá)式。

至此,@RegexValid 的聲明已經(jīng)結(jié)束。但是,程序仍不知道如何處理 @RegexValid 這個(gè)注解。我們還需要定義注解處理器。

4.3. 注解處理器

如果沒有用來讀取注解的方法和工作,那么注解也就不會(huì)比注釋更有用處了。使用注解的過程中,很重要的一部分就是創(chuàng)建于使用注解處理器。JDK5 擴(kuò)展了反射機(jī)制的 API,以幫助程序員快速的構(gòu)造自定義注解處理器。

java.lang.annotation.Annotation 是一個(gè)接口,程序可以通過反射來獲取指定程序元素的注解對(duì)象,然后通過注解對(duì)象來獲取注解里面的元數(shù)據(jù)。

Annotation 接口源碼如下:

public interface Annotation {
    boolean equals(Object obj);

    int hashCode();

    String toString();

    Class<? extends Annotation> annotationType();
}

除此之外,Java 中支持注解處理器接口 java.lang.reflect.AnnotatedElement ,該接口代表程序中可以接受注解的程序元素,該接口主要有如下幾個(gè)實(shí)現(xiàn)類:

  • Class - 類定義
  • Constructor - 構(gòu)造器定義
  • Field - 累的成員變量定義
  • Method - 類的方法定義
  • Package - 類的包定義

java.lang.reflect 包下主要包含一些實(shí)現(xiàn)反射功能的工具類。實(shí)際上,java.lang.reflect 包所有提供的反射 API 擴(kuò)充了讀取運(yùn)行時(shí)注解信息的能力。當(dāng)一個(gè)注解類型被定義為運(yùn)行時(shí)的注解后,該注解才能是運(yùn)行時(shí)可見,當(dāng) class 文件被裝載時(shí)被保存在 class 文件中的注解才會(huì)被虛擬機(jī)讀取。
AnnotatedElement 接口是所有程序元素(Class、Method 和 Constructor)的父接口,所以程序通過反射獲取了某個(gè)類的AnnotatedElement 對(duì)象之后,程序就可以調(diào)用該對(duì)象的如下四個(gè)個(gè)方法來訪問注解信息:

  • getAnnotation - 返回該程序元素上存在的、指定類型的注解,如果該類型注解不存在,則返回 null。
  • getAnnotations - 返回該程序元素上存在的所有注解。
  • isAnnotationPresent - 判斷該程序元素上是否包含指定類型的注解,存在則返回 true,否則返回 false。
  • getDeclaredAnnotations - 返回直接存在于此元素上的所有注釋。與此接口中的其他方法不同,該方法將忽略繼承的注釋。(如果沒有注釋直接存在于此元素上,則返回長(zhǎng)度為零的一個(gè)數(shù)組。)該方法的調(diào)用者可以隨意修改返回的數(shù)組;這不會(huì)對(duì)其他調(diào)用者返回的數(shù)組產(chǎn)生任何影響。

了解了以上內(nèi)容,讓我們來實(shí)現(xiàn) @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 為空字符串,說明沒有注入自定義正則表達(dá)式,改用 policy 屬性
                String value = valid.value();
                if ("".equals(value)) {
                    RegexValid.Policy policy = valid.policy();
                    value = policy.getPolicy();
                }

                // 通過設(shè)置 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 字段不是字符串類型,不能使用此注解校驗(yàn)!", obj.getClass().getName(), field.getName()));
                        result = false;
                    }
                }
            }
        }

        if (sb.length() > 0) {
            throw new Exception(sb.toString());
        }
        return result;
    }
}

說明:

以上示例中的注解處理器,執(zhí)行步驟如下:

  1. 通過 getDeclaredFields 反射方法獲取傳入對(duì)象的所有成員。
  2. 遍歷成員,使用 isAnnotationPresent 判斷成員是否被指定注解所修飾,如果不是,直接跳過。
  3. 如果成員被注解所修飾,通過 RegexValid valid = field.getAnnotation(RegexValid.class); 這樣的形式獲取,注解實(shí)例化對(duì)象,然后,就可以使用 valid.value()valid.policy() 這樣的形式獲取注解中設(shè)定的屬性值。
  4. 根據(jù)屬性值,進(jìn)行邏輯處理。

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 + "正則校驗(yàn)通過");
        }
        if (RegexValidUtil.check(user2)) {
            System.out.println(user2 + "正則校驗(yàn)通過");
        }
    }
}

5. 小結(jié)

img
img
img
img

6. 參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容