網(wǎng)站建設(shè)如何交稅南京seo報價
JDK 的編譯子系統(tǒng)暴露給用戶直接控制的功能相對很少,除了虛擬機即時編譯的若干參數(shù),便只有 JSR-296
中定義的插入式注解處理器 API;
文章目錄
- 1. 目標
- 2. 實現(xiàn)
- 3. 運行與測試
- 4. 其他應(yīng)用案例
1. 目標
前端編譯器在講 Java 源碼編譯成字節(jié)碼時對源碼做了各方面檢查,主要是看程序寫到對不對
,較少去看寫得好不好
,業(yè)界有一些如 CheckStyle、FindBug、Klocwork 等的工具用于檢查代碼好壞,有一些用于掃描 Java 源碼,也有一些是掃描字節(jié)碼;我們的目標是通過注解處理器 API 實現(xiàn)自己編碼風格的校驗工具:NameCheckProcessor
;
根據(jù)《Java 語言規(guī)范》要求,Java 程序命名推薦使用如下書寫規(guī)范:
- 類(接口):駝峰命名法,首字母大寫;
- 方法:駝峰命名法,首字母小寫;
- 字段
- 類或?qū)嵗兞?#xff1a;駝峰命名法,首字母小寫;
- 常量:全部由大寫字母或下劃線構(gòu)成,第一個字符不能是下劃線;
為 javac 編譯器添加額外的功能,在編譯程序時檢查程序名是否符合上述對類、接口、方法、字段的命名要求;
2. 實現(xiàn)
注解處理器可以通過繼承抽象類 javax.annotation.processing.AbstractProcessor
復(fù)寫抽象方法 process()
來實現(xiàn);javac 編譯器在執(zhí)行注解處理器代碼時會調(diào)用 process()
;
annotations
,此注解處理器所要處理的注解集合;roundEnv
,當前這個輪次(Round)中的抽象語法樹節(jié)點,每個語法樹節(jié)點表示一個 Element;processingEnv
,AbstractProcessor 中的一個 protected 變量,代表了注解處理器框架提供的一個上下文環(huán)境,可用來創(chuàng)建新的代碼、向編譯器輸出信息、獲取其他工具類等;
javax.lang.model.ElementKind 定義了 18 類 Element,包含 Java 代碼中可能出現(xiàn)的全部元素,如包(Package)、枚舉(Enum)、類(Class)、注解(AnnotationType)、接口(Interface)、枚舉值(EnumConstant)、字段(Field)、參數(shù)(Parameter)、本地變量(LocalVariable)、異常(ExceptionnParameter)、方法(Method)、構(gòu)造函數(shù)(Constructor)、靜態(tài)語句塊(StaticInit,即 static{} 塊)、實例語句塊(InstanceInit,即 {} 塊)、參數(shù)化類型(TypeParameter,泛型尖括號內(nèi)的類型)、資源變量(ResourceVariable,try-resource 中定義的變量)、模塊(Module)、未定義的其他語法樹節(jié)點(Other);
@SupportedAnnotationTypes
,代表這個注解處理器對哪些注解感興趣;@SuppertedSourceVersion
,表示這個注解處理器可以處理哪些版本的 Java 代碼;
注解處理器 NameCheckProcessor
/*** "*" 表示支持所有 Annotations* 只支持 JDK 8 的 Java 代碼** @author Aurelius Shu* @since 2023-02-19*/
@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class NameCheckProcessor extends AbstractProcessor {private NameChecker nameChecker;/*** 初始化名稱檢查插件*/@Overridepublic void init(ProcessingEnvironment processingEnv) {super.init(processingEnv);nameChecker = new NameChecker(processingEnv);}/*** 對輸入的語法樹的各個節(jié)點進行名稱檢查*/@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {if (!roundEnv.processingOver()) {for (Element element : roundEnv.getRootElements())nameChecker.checkNames(element);}return false;}
}
命名檢查器 NameChecker
/*** 程序名稱規(guī)范的編譯器插件* 如果程序命名不合規(guī)范,將會輸出一個編譯器的 WARNING 信息** @author Aurelius Shu* @since 2023-02-19*/
public class NameChecker {private final Messager messager;NameCheckScanner nameCheckScanner = new NameCheckScanner();NameChecker(ProcessingEnvironment processsingEnv) {this.messager = processsingEnv.getMessager();}/*** 對Java程序命名進行檢查,根據(jù)《Java語言規(guī)范》第三版第 6.8 節(jié)的要求,Java程序命名應(yīng)當符合下列格式: * 類或接口:符合駝式命名法,首字母大寫。* 方法:符合駝式命名法,首字母小寫。* 字段:* 類、實例變量: 符合駝式命名法,首字母小寫。* 常量: 要求全部大寫。*/public void checkNames(Element element) {nameCheckScanner.scan(element);}/*** 名稱檢查器實現(xiàn)類,繼承了 JDK 8 中新提供的ElementScanner8<br> * 將會以 Visitor 模式訪問抽象語法樹中的元素*/private class NameCheckScanner extends ElementScanner8<Void, Void> {/*** 此方法用于檢查 Java 類*/@Overridepublic Void visitType(TypeElement e, Void p) {scan(e.getTypeParameters(), p);checkCamelCase(e, true);super.visitType(e, p);return null;}/*** 檢查方法命名是否合法*/@Overridepublic Void visitExecutable(ExecutableElement e, Void p) {if (e.getKind() == METHOD) {Name name = e.getSimpleName();if (name.contentEquals(e.getEnclosingElement().getSimpleName())) {messager.printMessage(WARNING, "一個普通方法 “" + name + "”不應(yīng)當與類名重復(fù),避免與構(gòu)造函數(shù)產(chǎn)生混淆", e);}checkCamelCase(e, false);}super.visitExecutable(e, p);return null;}/*** 檢查變量命名是否合法*/@Overridepublic Void visitVariable(VariableElement e, Void p) {// 如果這個Variable是枚舉或常量,則按大寫命名檢查,否則按照駝式命名法規(guī)則檢查if (e.getKind() == ENUM_CONSTANT || e.getConstantValue() != null || heuristicallyConstant(e))checkAllCaps(e);else checkCamelCase(e, false);return null;}/*** 判斷一個變量是否是常量*/private boolean heuristicallyConstant(VariableElement e) {if (e.getEnclosingElement().getKind() == INTERFACE) return true;else if (e.getKind() == FIELD && e.getModifiers().containsAll(EnumSet.of(PUBLIC, STATIC, FINAL)))return true;else {return false;}}/*** 檢查傳入的 Element 是否符合駝式命名法,如果不符合,則輸出警告信息*/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 + "”應(yīng)當以小寫字母開頭", e);return;}} else if (Character.isLowerCase(firstCodePoint)) {if (initialCaps) {messager.printMessage(WARNING, "名稱“" + name + "”應(yīng)當以大寫字母開頭", 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 + "”應(yīng)當符合駝式命名法(Camel Case Names)", 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 + "”應(yīng)當全部以大寫字母或下劃線命名,并且以字母開頭", e);}}}
}
NameChecker
通過繼承 javax.lang.model.util.ElementScanner8
的 NameCheckScanner
類,以 Visitor 模式實現(xiàn)對語法樹的遍歷;通過 checkCamelCase() 與 checkAllCaps() 實現(xiàn)駝峰命名法和全大寫命名的檢查;分別在 visitType()、visitVariable()、visitExecutable() 中對類、字段、方法進行檢查;
3. 運行與測試
不符合規(guī)范的代碼演示
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() {return;}public void NOTcamelCASEmethodNAME() {return;}
}
運行注解處理器
# 編譯注解處理器,切換到 src/main/java 路徑
javac edu/aurelius/jvm/compiler/NameChecker.java
javac edu/aurelius/jvm/compiler/NameCheckProcessor.java# 編譯時運行注解處理器
javac -processor edu.aurelius.jvm.compiler.NameCheckProcessor edu/aurelius/jvm/compiler/BADLY_NAMED_CODE.java
檢查效果
edu/aurelius/jvm/compiler/BADLY_NAMED_CODE.java:7: warning: 名稱“BADLY_NAMED_CODE”應(yīng)當符合駝式命名法(Camel Case Names)
public class BADLY_NAMED_CODE {^
edu/aurelius/jvm/compiler/BADLY_NAMED_CODE.java:8: warning: 名稱“colors”應(yīng)當以大寫字母開頭enum colors {^
edu/aurelius/jvm/compiler/BADLY_NAMED_CODE.java:9: warning: 常量“red”應(yīng)當全部以大寫字母或下劃線命名,并且以字母開頭red, blue, green;^
edu/aurelius/jvm/compiler/BADLY_NAMED_CODE.java:9: warning: 常量“blue”應(yīng)當全部以大寫字母或下劃線命名,并且以字母開頭red, blue, green;^
edu/aurelius/jvm/compiler/BADLY_NAMED_CODE.java:9: warning: 常量“green”應(yīng)當全部以大寫字母或下劃線命名,并且以字母開頭red, blue, green;^
edu/aurelius/jvm/compiler/BADLY_NAMED_CODE.java:12: warning: 常量“_FORTY_TWO”應(yīng)當全部以大寫字母或下劃線命名,并且以字母開頭static final int _FORTY_TWO = 42;^
edu/aurelius/jvm/compiler/BADLY_NAMED_CODE.java:13: warning: 名稱“NOT_A_CONSTANT”應(yīng)當以小寫字母開頭public static int NOT_A_CONSTANT = _FORTY_TWO;^
edu/aurelius/jvm/compiler/BADLY_NAMED_CODE.java:15: warning: 一個普通方法 “BADLY_NAMED_CODE”不應(yīng)當與類名重復(fù),避免與構(gòu)造函數(shù)產(chǎn)生混淆protected void BADLY_NAMED_CODE() {^
edu/aurelius/jvm/compiler/BADLY_NAMED_CODE.java:15: warning: 名稱“BADLY_NAMED_CODE”應(yīng)當以小寫字母開頭protected void BADLY_NAMED_CODE() {^
edu/aurelius/jvm/compiler/BADLY_NAMED_CODE.java:19: warning: 名稱“NOTcamelCASEmethodNAME”應(yīng)當以小寫字母開頭public void NOTcamelCASEmethodNAME() {^
10 warnings
-XprintRounds
、-XprintProcessorInfo
參數(shù)可以進一步跟蹤注解處理器的運作詳細信息;
4. 其他應(yīng)用案例
- 校驗 Hibernate 標簽的正確性(Hibernate Validator Annotation Processor);
- 自動為字段生成 getter、setter 方法等輔助內(nèi)容(Lombok,根據(jù)已有元素生成新的語法樹元素);
上一篇:「JVM 編譯優(yōu)化」Java 語法糖(泛型、自動裝箱/拆箱、條件編譯)
PS:感謝每一位志同道合者的閱讀,歡迎關(guān)注、評論、贊!
參考資料:
- [1]《深入理解 Java 虛擬機》