插入式注解處理器


Lombok就用到插入式注解處理器,Lombok通過注解的方式,在編譯時自動為屬性生成構造器、getter/setter、equals、hashcode、toString等方法。

插入式注解處理器是JSR-269中定義的API,該API可以在編譯期對代碼中的特定注解進行處理,從而影響到前端編譯器的工作過程,通過插入式注解處理器可以讀取、修改、添加抽象語法樹中的任意元素,這樣就可以實現很多很cool的功能。

示例:

很多IDEA都有代碼校驗插件,這里我們使用注解處理器API來編寫自己的編碼風格校驗工具:NameCheckProcessor。

主要功能是在執行javac命令編譯java文件時,校驗代碼命名是否符合以下的《Java語言規范》,如果不符合則輸出警告信息。

  • 類(或接口):符合駝式命名法,首字母大寫。
  • 方法:符合駝式命名法,首字母小寫
  • 字段
    類或實例變量:符合駝式命名法,首字母小寫。
    常量:要求全部由大寫字母或下划線構成,且第一個字符不能是下划線。

代碼實現

實現的注解處理器需要繼承抽象類javax.annotation.processing.AbstractProcessor,並且子類必須實現抽象方法process()。

注解處理器NameCheckProcessor

package compile;
import javax.annotation.processing.AbstractProcessor; 
import javax.annotation.processing.ProcessingEnvironment; 
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 java.util.Set; 
 
/** 
 * 代碼命名規范校驗注解處理器 
 */ 
@SupportedAnnotationTypes("*")// 表示對哪些注解感興趣 
@SupportedSourceVersion(SourceVersion.RELEASE_8)// 需要處理哪個版本的Java代碼 
public class NameCheckProcessor extends AbstractProcessor { 
 
    private NameChecker nameChecker; 
 
    @Override 
    public synchronized void init(ProcessingEnvironment processingEnv) { 
        super.init(processingEnv); 
        nameChecker = new NameChecker(processingEnv); 
    } 
 
    /** 
     * 對輸入的語法樹的各個節點進行名稱檢查 
     * @param annotations  獲取此注解處理器要處理的注解集合 
     * @param roundEnv 從該參數訪問到當前這個輪次(Round)中的抽象語法樹節點 
     * @return 
     */ 
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { 
        if (!roundEnv.processingOver()){ 
            for(Element element : roundEnv.getRootElements()){ 
                nameChecker.checkNames(element); 
            } 
        } 
        // 返回false通知該倫次中代碼並未改變 
        return false; 
    } 
} 

processingEnv是AbstractProcessor的一個protected變量,在執行init()方法時創建,代碼注解處理器框架的一個上下文環境,要創建新的代碼、向編譯器輸出信息、獲取其他工具類等都需要這個實例變量。

命名檢查器NameChecker

package compile;
import javax.annotation.processing.Messager; 
import javax.annotation.processing.ProcessingEnvironment; 
import javax.lang.model.element.Element; 
import javax.lang.model.element.ElementKind; 
import javax.lang.model.element.ExecutableElement; 
import javax.lang.model.element.Modifier; 
import javax.lang.model.element.Name; 
import javax.lang.model.element.TypeElement; 
import javax.lang.model.element.VariableElement; 
import javax.lang.model.util.ElementScanner8; 
 
import java.util.EnumSet; 
 
import static javax.tools.Diagnostic.Kind.WARNING; 
 
/** 
 * 程序名稱規范的編譯器插件:<br> 
 * 如果程序命名不合規范,將會輸出一個編譯器的WANING信息 
 */ 
public class NameChecker { 
 
    private final Messager messager; 
 
    private NameCheckScanner nameCheckScanner = new NameCheckScanner(); 
 
    public NameChecker(ProcessingEnvironment processingEnv) { 
        this.messager = processingEnv.getMessager(); 
    } 
 
    /** 
     * @param element 
     */ 
    public void checkNames(Element element) { 
        nameCheckScanner.scan(element); 
    } 
 
    /** 
     * 名稱檢查器實現類,繼承了JDK 8中提供的ElementScanner8, 
     * 將會以Visitor模式訪問抽象語法樹中元素 
     */ 
    private class NameCheckScanner extends ElementScanner8<Void, Void> { 
 
        /** 
         * 檢查變量命名是否合法 
         * 
         * @param e 
         * @param p 
         * @return 
         */ 
        @Override 
        public Void visitVariable(VariableElement e, Void p) { 
            // 如果這個變量是常量或枚舉,則按照大寫命名檢查,否則按照駝式命名法規則檢查 
            if (e.getKind() == ElementKind.ENUM_CONSTANT || e.getConstantValue() != null || 
                    heuristicallyConstant(e)) { 
                checkAllCaps(e); 
            } else { 
                checkCamelCase(e, false); 
            } 
            return null; 
        } 
 
 
        /** 
         * 判斷一個變量是否為常量 
         * 
         * @param e 
         * @return 
         */ 
        private boolean heuristicallyConstant(VariableElement e) { 
            if (e.getEnclosingElement().getKind() == ElementKind.INTERFACE) { 
                return true; 
            } else if (e.getKind() == ElementKind.FIELD && 
                    e.getModifiers().containsAll(EnumSet.of(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL))) { 
                return true; 
            } 
            return false; 
        } 
 
        /** 
         * 檢查類名是否合法 
         * 
         * @param e 
         * @param p 
         * @return 
         */ 
        @Override 
        public Void visitType(TypeElement e, Void p) { 
            scan(e.getTypeParameters(), p); 
            checkCamelCase(e, true); 
            super.visitType(e, p); 
            return null; 
        } 
 
 
        /** 
         * 大寫命名檢查 
         * 要求第一個字母必須是大寫的英文字母,其余部門可以是下划線或大寫字母 
         * 
         * @param e 
         */ 
        private void checkAllCaps(Element e) { 
            String name = e.getSimpleName().toString(); 
            boolean conventional = true; 
            int firstCodePoint = name.codePointAt(0); 
            if (!Character.isUpperCase(firstCodePoint)) { 
                // 第一個字符不是大寫字母 
                conventional = false; 
            } else { 
                boolean previousUnderscore = false; 
                int cp = firstCodePoint; 
                for (int i = Character.charCount(cp); i < name.length(); i += Character.charCount(cp)) { 
                    cp = name.codePointAt(i); 
                    if (cp == (int) '_') { 
                        if (previousUnderscore) { 
                            // 連續兩個_ 
                            conventional = false; 
                            break; 
                        } 
                        previousUnderscore = true; 
                    } else { 
                        previousUnderscore = false; 
                        if (!Character.isUpperCase(cp) && !Character.isDigit(cp)) { 
                            conventional = false; 
                            break; 
                        } 
                    } 
                } 
            } 
 
            if (!conventional) { 
                messager.printMessage(WARNING, "常量 " + name + " 應該全部以大寫字母或下划線命名,並且以字母開頭", e); 
            } 
        } 
 
 
        /** 
         * 檢查傳入的Element是否符合駝峰命名法,如果不符合輸出警告信息 
         * 
         * @param e 
         * @param initialCaps 
         */ 
        private void checkCamelCase(Element e, boolean initialCaps) { 
            String name = e.getSimpleName().toString(); 
            // 上個字母是否大寫 
            boolean previousUpper = false; 
            boolean conventional = true; 
            int firstCodePoint = name.codePointAt(0); 
 
            if (Character.isUpperCase(firstCodePoint)) { 
                previousUpper = true; 
                if (!initialCaps) { 
                    messager.printMessage(WARNING, "名稱 " + name + " 應該以小寫字母開頭", e); 
                    return; 
                } 
            } else if (Character.isLowerCase(firstCodePoint)) { 
                if (initialCaps) { 
                    messager.printMessage(WARNING, "名稱 " + name + " 應該以大寫字母開頭", e); 
                    return; 
                } 
            } else { 
                conventional = false; 
            } 
 
            if (conventional) { 
                int cp = firstCodePoint; 
                for (int i = Character.charCount(cp); i < name.length(); i += Character.charCount(cp)) { 
                    cp = name.codePointAt(i); 
                    if (Character.isUpperCase(cp)) { 
                        if (previousUpper) { 
                            conventional = false; 
                            break; 
                        } 
                        previousUpper = true; 
                    } else { 
                        previousUpper = false; 
                    } 
                } 
            } 
 
            if (!conventional) { 
                messager.printMessage(WARNING, "名稱 " + name + " 應該符合駝式命名法(Camel Case Names)", e); 
            } 
        } 
 
        /** 
         * 檢查方法命名是否合法 
         * 
         * @param e 
         * @param p 
         * @return 
         */ 
        @Override 
        public Void visitExecutable(ExecutableElement e, Void p) { 
            if (e.getKind() == ElementKind.METHOD) { 
                Name name = e.getSimpleName(); 
                if (name.contentEquals(e.getEnclosingElement().getSimpleName())) { 
                    messager.printMessage(WARNING, "一個普通方法 " + name + " 不應當與類名重復,避免與構造函數產生混淆", e); 
                } 
                checkCamelCase(e, false); 
            } 
            super.visitExecutable(e, p); 
            return null; 
        } 
    } 
 
} 

javax.lang.model.element.ElementKind是個枚舉類,里面定義了18種Element包括了Java代碼中可能出現的全部元素。

測試

首先寫一個命名不規范的代碼樣例

package compile;
public class BADLY_NAMED_CODE { 
 
    enum colors { 
        red, blue, green; 
    } 
 
    static final int _FORTY_TWO = 42; 
 
    public static int NOT_A_CONSTANT = _FORTY_TWO; 
 
    protected void BADLY_NAMED_CODE() { 
 
    } 
 
    public void NOTcamelCASEmethodNAME() { 
        return; 
    } 
    public static void main(String[] args) {
        System.out.println("nihao");
    }
}
  • 編譯NameChecker.java
  • 編譯NameCheckProcessor.java
  • 編譯BADLY_NAMED_CODE.java
D:\gitspace\Test\src>javac  compile/NameChecker.java

D:\gitspace\Test\src>javac compile/NameCheckProcessor.java

D:\gitspace\Test\src>javac -processor compile.NameCheckProcessor compile/BADLY_NAMED_CODE.java
compile\BADLY_NAMED_CODE.java:2: 警告: 名稱 BADLY_NAMED_CODE 應該符合駝式命名法(Camel Case Names)
public class BADLY_NAMED_CODE {
       ^
compile\BADLY_NAMED_CODE.java:4: 警告: 名稱 colors 應該以大寫字母開頭
    enum colors {
    ^
compile\BADLY_NAMED_CODE.java:5: 警告: 常量 red 應該全部以大寫字母或下划線命名,並且以字母開頭
        red, blue, green;
        ^
compile\BADLY_NAMED_CODE.java:5: 警告: 常量 blue 應該全部以大寫字母或下划線命名,並且以字母開頭
        red, blue, green;
             ^
compile\BADLY_NAMED_CODE.java:5: 警告: 常量 green 應該全部以大寫字母或下划線命名,並且以字母開頭
        red, blue, green;
                   ^
compile\BADLY_NAMED_CODE.java:8: 警告: 常量 _FORTY_TWO 應該全部以大寫字母或下划線命名,並且以字母開頭
    static final int _FORTY_TWO = 42;
                     ^
compile\BADLY_NAMED_CODE.java:10: 警告: 名稱 NOT_A_CONSTANT 應該以小寫字母開頭
    public static int NOT_A_CONSTANT = _FORTY_TWO;
                      ^
compile\BADLY_NAMED_CODE.java:12: 警告: 一個普通方法 BADLY_NAMED_CODE 不應當與類名重復,避免與構造函數產生混淆
    protected void BADLY_NAMED_CODE() {
                   ^
compile\BADLY_NAMED_CODE.java:12: 警告: 名稱 BADLY_NAMED_CODE 應該以小寫字母開頭
    protected void BADLY_NAMED_CODE() {
                   ^
compile\BADLY_NAMED_CODE.java:16: 警告: 名稱 NOTcamelCASEmethodNAME 應該以小寫字母開頭
    public void NOTcamelCASEmethodNAME() {
                ^
10 個警告

D:\gitspace\Test\src>

注意: 這里需要用-processor參數指定用到的注解處理器,如果是多個的話用逗號分隔。

或者:

package compile;

import javax.tools.ToolProvider;

public class Test {

    public static void main(String[] args) {
        javax.tools.JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        int results = compiler.run(null, null, null, new String[] {
                "-processor", "compile.NameCheckProcessor",
                "-processorpath", "D:/gitspace/Test/bin/",
                "-d", "E:/javaclass",
                "D:/gitspace/Test/src/compile/BADLY_NAMED_CODE.java"
                
        });
        System.out.println(results);
    }
}

結果輸出:

D:\gitspace\Test\src\compile\BADLY_NAMED_CODE.java:2: 警告: 名稱 BADLY_NAMED_CODE 應該符合駝式命名法(Camel Case Names)
public class BADLY_NAMED_CODE { 
       ^
D:\gitspace\Test\src\compile\BADLY_NAMED_CODE.java:4: 警告: 名稱 colors 應該以大寫字母開頭
    enum colors { 
    ^
D:\gitspace\Test\src\compile\BADLY_NAMED_CODE.java:5: 警告: 常量 red 應該全部以大寫字母或下划線命名,並且以字母開頭
        red, blue, green; 
        ^
D:\gitspace\Test\src\compile\BADLY_NAMED_CODE.java:5: 警告: 常量 blue 應該全部以大寫字母或下划線命名,並且以字母開頭
        red, blue, green; 
             ^
D:\gitspace\Test\src\compile\BADLY_NAMED_CODE.java:5: 警告: 常量 green 應該全部以大寫字母或下划線命名,並且以字母開頭
        red, blue, green; 
                   ^
D:\gitspace\Test\src\compile\BADLY_NAMED_CODE.java:8: 警告: 常量 _FORTY_TWO 應該全部以大寫字母或下划線命名,並且以字母開頭
    static final int _FORTY_TWO = 42; 
                     ^
D:\gitspace\Test\src\compile\BADLY_NAMED_CODE.java:10: 警告: 名稱 NOT_A_CONSTANT 應該以小寫字母開頭
    public static int NOT_A_CONSTANT = _FORTY_TWO; 
                      ^
D:\gitspace\Test\src\compile\BADLY_NAMED_CODE.java:12: 警告: 一個普通方法 BADLY_NAMED_CODE 不應當與類名重復,避免與構造函數產生混淆
    protected void BADLY_NAMED_CODE() { 
                   ^
D:\gitspace\Test\src\compile\BADLY_NAMED_CODE.java:12: 警告: 名稱 BADLY_NAMED_CODE 應該以小寫字母開頭
    protected void BADLY_NAMED_CODE() { 
                   ^
D:\gitspace\Test\src\compile\BADLY_NAMED_CODE.java:16: 警告: 名稱 NOTcamelCASEmethodNAME 應該以小寫字母開頭
    public void NOTcamelCASEmethodNAME() { 
                ^
10 個警告
0

 

 

 參考:https://www.cnblogs.com/2YSP/p/12919880.html

參考《Java編譯器》


免責聲明!

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



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