關於JAVA中源碼級注解的編寫及使用


一、注解簡介:

1.1.什么是“注解”:

​ 在我們編寫代碼時,一定看到過這樣的代碼:

class Student {
    private String name;

    @Override
    public String toString(String str) {//編譯錯誤!
        return "Student name = " + name;
    }
}

​ 其中的@Override,就是一個“注解”,@Override一般出現在重寫equals()或者toString()方法的上邊,意思是告訴編譯器:下邊的代碼是重寫父類方法的。這時編譯器會按照“重寫”的語法嚴格檢查下面的方法,如果不符合重寫語法,將會編譯錯誤。

​ "注解"作為一種“標記”,被寫在源碼中,不會改變程序的執行流程。它通常由“注解解析工具”來解析,而“注解解析器”可以隨Java編譯器啟動,也可以獨立啟動,來解析注解,並以此可以做一些事情。

1.2.注解的分類:

源碼注解:

​ 注解只在源碼中,編譯成class文件后就不存在了。

編譯時注解:

​ 注解在源碼和.class文件中都存在(如:JDK內置系統注解)

運行時注解:

​ 在運行階段還起作用,甚至會影響運行邏輯的注解(如:JUnit的@Test)

1.3.注解的作用

​ 注解的作用非常廣泛,注解可以被用在類、屬性、構造方法、成員方法、局部變量等位置,用於對這些元素進行說明。由“注解解析工具”解析后,可以生成文檔、進行代碼分析、編譯檢查等。
​ 本例將會實現一個用作"編譯檢查“的注解,以及一個"注解解析器"。"注解解析器"將會隨着javac編譯器一同啟動來對使用了注解的類進行編譯,並檢查類名、字段名、方法名是否以大寫、小寫字符開頭,如果違反了規則,編譯時將會報錯。

二、自定義注解:

2.1.定義注解的基本語法

​ “注解”本質上是一個“類”,我們可以根據自己的需要定義自己的注解。
​ 定義注解的語法很簡單:

public @interface CheckWord{
	...
}

​ "注解”編譯后會生成.class文件。但這是一個非常簡單的注解,它可以被用在任何位置,而且編譯器遇到這種注解也不做任何事情。例如:

@CheckWord
public class Student {
    @CheckWord
    public Student() {
    }

    @CheckWord
    private String name;

    @CheckWord
    public void study() {
    }
}

下面我們先使用“元注解”來規定這個注解可以被用在哪里。

2.2.元注解

​ “元注解”也是一種“注解”,它是已經實現好的。必須用在“注解”的定義上,它可以規定注解可以用在哪里,以及可以存在於源碼中,或者class中,或者運行時。
常用的“元注解”有兩個:
​ 1).@Target : 規定注解可以用在哪里。常用的取值被定義在枚舉java.lang.annotation.ElementType中:
​ ElementType.TYPE:類和接口上
​ ElementType.FIELD: 用在成員變量上
​ ElementType.METHOD: 用在方法上
​ ElementType.PARAMETER: 用在參數上
​ ElementType.CONSTRUCTOR: 用在構造方法上
​ ElementType.LOCAL_VARIABLE: 用在局部變量上
​ 2).@Retention : 規定注解可以存在於哪里。常用的取值被定義在枚舉java.lang.annotation.RetentionPolicy中:
​ RetentionPolicy.SOURCE: 規定注解只存在於Java源代碼中, 編譯生成的字節碼文件中就不存在了。
​ RetentionPolicy.CLASS: 規定注解存在於Java源代碼、 編譯以后的字節碼文件中, 但JVM運行時,不會被加載到內存。
​ RetentionPolicy.RUNTIME: 規定注解存在於Java源代碼中、 編譯以后的字節碼文件中、 運行時內存中, 程序可以通過反射獲取該注解。
​ 例如:修改我們的注解,規定它只能用在"類","字段",“方法”上,並且可以存在於“源碼中”:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface CheckWord {

}

​ 如果再編譯之前的Student類,會發現用在"構造方法"上的@CheckWord會編譯錯誤,因為我們規定了它只能用在"類","字段","方法"上。

2.3.定義注解的屬性:

1.“注解”中可以定義一些屬性,“注解解析器”可以根據“屬性”的不同,分別做不同的事情。

​ 例如@Target注解中的ElementType.TYPE就是此注解的一個屬性,它是一個"枚舉"類型。
​ 下面讓我們來看看怎樣定義屬性,然后再解析這些屬性。
​ 注解中定義屬性的語法:數據類型 屬性名() [deafult 值];
​ 1.其中“數據類型”可以是:
​ 1).所有基本類型;
​ 2).String;
​ 3).Class;
​ 4).枚舉;
​ 5).注解;
​ 6).以上任一類型的數組
​ 2.屬性名():屬性名可以自由設定,要遵循Java標識符的命名規則;其中的一對()是必須的。
​ 3.[default 值]:為此屬性設置的默認值。
2.本例中由於只檢查大小寫,為了規范取值,所以定義一個"枚舉"類型的屬性。
​ 1).先定義枚舉:

public enum StartsWith {
    UPPERCASE, LOWERCASE
}

​ 此枚舉定義了兩個值:UPPERCASE表示:大寫;LOWERCASE表示:小寫。
​ 2).修改"CheckWord"注解的代碼:

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface CheckWord {
    StartsWith value();
}

​ 說明:
​ a.StartsWith表示"數據類型",是一個"枚舉"類型。
​ b.value表示"屬性名",在使用此注解時,此屬性的可取值只有StartsWith.UPPERCASE和StartsWith.LOWERCASE兩個。
​ c.此屬性沒有設置"默認值",在使用此注解時必須要設置此屬性的值。如下面的代碼:
​ 3).修改"Student"類的代碼:

@CheckWord(StartsWith.UPPERCASE)
public class Student {
    @CheckWord(StartsWith.LOWERCASE)
    private String stuName;
    @CheckWord(StartsWith.LOWERCASE)
    public void show() {
    }
}

2.4注解解析器:

​ 1."注解解析器"通常是隨着注解一起定義的,用於解析"注解",並做一些事情。本例的"注解解析器"用於與javac編譯器一起啟動,編譯Student類時,檢查各元素的名稱是否按要求以指定的大寫、小寫字母開頭。
​ 2.自定義"注解解析器"需要繼承AbstractProcessor類,並重寫process()方法,完整的"注解解析器"代碼如下:

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set;

@SupportedAnnotationTypes("CheckWord")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        //獲取所有使用了@CheckWord注解的元素
        Set<? extends Element> annoEle = roundEnv.getElementsAnnotatedWith(CheckWord.class);
        // 遍歷這些元素
        for (Element e : annoEle) {
            //獲取元素名稱,可能是:類名、屬性名、方法名
            String name = e.getSimpleName().toString();
            //獲取這個名字的第一個字母
            char c = name.charAt(0);
            //獲取這個元素上的@CheckWord注解對象
            CheckWord anno = e.getAnnotation(CheckWord.class);
            //獲取這個注解的value屬性的值,它是一個StartsWith枚舉類型
            StartsWith sw = anno.value();
            //判斷屬性值是否設置為:StartsWith.UPPERCASE,但名字的首字母是小寫
            if (sw == StartsWith.UPPERCASE && Character.isLowerCase(c)) {
                //向控制台打印異常信息
                this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "名稱:" + name + " 首字母應該大寫!");
                return false;
            }
            //判斷屬性值是否設置為:StartsWith.LOWERCASE,但名字的首字母是大寫
            if (sw == StartsWith.LOWERCASE && Character.isUpperCase(c)) {
                this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "名稱:" + name + " 首字母應該小寫!");
                return false;
            }

        }
        return true;
    }
}

​ 此代碼的細節大家可以根據注釋一點一點研究。一些類:TypeElement,RoundEnvironment,Element等的一些方法大家可以在API手冊中查找。
​ 其它說明:
​ @SupportedAnnotationTypes("CheckWord") : 表示只處理CheckWord注解。
​ @SupportedSourceVersion(SourceVersion.RELEASE_8) : 表示支持JDK1.8。

2.5.編譯和測試:

1.在編譯前,我們看一下完整的代碼清單:請確保以下的四個類在同一個目錄下
​ 1).枚舉類:

public enum StartsWith {
    UPPERCASE, LOWERCASE
}			

​ 2).自定義注解類:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface CheckWord {
    StartsWith value();
}

​ 3).使用了CheckWord注解的Student類:

@CheckWord(StartsWith.UPPERCASE)
public class Student {
    @CheckWord(StartsWith.LOWERCASE)
    private String StuName;

    @CheckWord(StartsWith.LOWERCASE)
    public void show() {

    }
}

​ 4).注解解析器類:

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.Set;

@SupportedAnnotationTypes("CheckWord")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class MyProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Set<? extends Element> annoEle = roundEnv.getElementsAnnotatedWith(CheckWord.class);
        for (Element e : annoEle) {
            String name = e.getSimpleName().toString();
            char c = name.charAt(0);

            CheckWord anno = e.getAnnotation(CheckWord.class);
            StartsWith sw = anno.value();
            if (sw == StartsWith.UPPERCASE) {
                if (Character.isLowerCase(c)) {
                    this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "名稱:" + name + " 首字母應該大寫!");
                    return false;
                }
            }
            if (sw == StartsWith.LOWERCASE) {
                if (Character.isUpperCase(c)) {
                    this.processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "名稱:" + name + " 首字母應該小寫!");
                    return false;
                }
            }
        }
        return true;
    }
}

2.啟動命令行,使用javac依次進行編譯:
​ javac StartsWith.java
​ javac CheckWord.java
​ javac MyProcessor.java(如果報錯: 編碼GBK的不可映射字符,是因為代碼中的中文,可以使用javac -encoding UTF-8 MyProcessor.java進行編譯)
​ 接下來使用MyProcessor解析器編譯Student:
​ javac -processor MyProcessor Student.java
​ 執行命令后,會有錯誤提示:
​ 錯誤: 名稱:StuName 首字母應該小寫!
​ 1 個錯誤

三、總結:

​ 源碼級注解的應用非常廣泛,例如:進行代碼檢查、生成新類、生成文件。本文實現了基本的代碼檢查,用於檢查類中的元素是否按照要求進行首字母大寫或者小寫。也可以根據需要,驗證是否全部大寫,或者全部小寫。希望大家通過本案例能夠了解源碼級注解的編寫及使用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM