zoukankan      html  css  js  c++  java
  • 插入式注解处理器

    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:gitspaceTestsrc>javac  compile/NameChecker.java
    
    D:gitspaceTestsrc>javac compile/NameCheckProcessor.java
    
    D:gitspaceTestsrc>javac -processor compile.NameCheckProcessor compile/BADLY_NAMED_CODE.java
    compileBADLY_NAMED_CODE.java:2: 警告: 名称 BADLY_NAMED_CODE 应该符合驼式命名法(Camel Case Names)
    public class BADLY_NAMED_CODE {
           ^
    compileBADLY_NAMED_CODE.java:4: 警告: 名称 colors 应该以大写字母开头
        enum colors {
        ^
    compileBADLY_NAMED_CODE.java:5: 警告: 常量 red 应该全部以大写字母或下划线命名,并且以字母开头
            red, blue, green;
            ^
    compileBADLY_NAMED_CODE.java:5: 警告: 常量 blue 应该全部以大写字母或下划线命名,并且以字母开头
            red, blue, green;
                 ^
    compileBADLY_NAMED_CODE.java:5: 警告: 常量 green 应该全部以大写字母或下划线命名,并且以字母开头
            red, blue, green;
                       ^
    compileBADLY_NAMED_CODE.java:8: 警告: 常量 _FORTY_TWO 应该全部以大写字母或下划线命名,并且以字母开头
        static final int _FORTY_TWO = 42;
                         ^
    compileBADLY_NAMED_CODE.java:10: 警告: 名称 NOT_A_CONSTANT 应该以小写字母开头
        public static int NOT_A_CONSTANT = _FORTY_TWO;
                          ^
    compileBADLY_NAMED_CODE.java:12: 警告: 一个普通方法 BADLY_NAMED_CODE 不应当与类名重复,避免与构造函数产生混淆
        protected void BADLY_NAMED_CODE() {
                       ^
    compileBADLY_NAMED_CODE.java:12: 警告: 名称 BADLY_NAMED_CODE 应该以小写字母开头
        protected void BADLY_NAMED_CODE() {
                       ^
    compileBADLY_NAMED_CODE.java:16: 警告: 名称 NOTcamelCASEmethodNAME 应该以小写字母开头
        public void NOTcamelCASEmethodNAME() {
                    ^
    10 个警告
    
    D:gitspaceTestsrc>

    注意: 这里需要用-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:gitspaceTestsrccompileBADLY_NAMED_CODE.java:2: 警告: 名称 BADLY_NAMED_CODE 应该符合驼式命名法(Camel Case Names)
    public class BADLY_NAMED_CODE { 
           ^
    D:gitspaceTestsrccompileBADLY_NAMED_CODE.java:4: 警告: 名称 colors 应该以大写字母开头
        enum colors { 
        ^
    D:gitspaceTestsrccompileBADLY_NAMED_CODE.java:5: 警告: 常量 red 应该全部以大写字母或下划线命名,并且以字母开头
            red, blue, green; 
            ^
    D:gitspaceTestsrccompileBADLY_NAMED_CODE.java:5: 警告: 常量 blue 应该全部以大写字母或下划线命名,并且以字母开头
            red, blue, green; 
                 ^
    D:gitspaceTestsrccompileBADLY_NAMED_CODE.java:5: 警告: 常量 green 应该全部以大写字母或下划线命名,并且以字母开头
            red, blue, green; 
                       ^
    D:gitspaceTestsrccompileBADLY_NAMED_CODE.java:8: 警告: 常量 _FORTY_TWO 应该全部以大写字母或下划线命名,并且以字母开头
        static final int _FORTY_TWO = 42; 
                         ^
    D:gitspaceTestsrccompileBADLY_NAMED_CODE.java:10: 警告: 名称 NOT_A_CONSTANT 应该以小写字母开头
        public static int NOT_A_CONSTANT = _FORTY_TWO; 
                          ^
    D:gitspaceTestsrccompileBADLY_NAMED_CODE.java:12: 警告: 一个普通方法 BADLY_NAMED_CODE 不应当与类名重复,避免与构造函数产生混淆
        protected void BADLY_NAMED_CODE() { 
                       ^
    D:gitspaceTestsrccompileBADLY_NAMED_CODE.java:12: 警告: 名称 BADLY_NAMED_CODE 应该以小写字母开头
        protected void BADLY_NAMED_CODE() { 
                       ^
    D:gitspaceTestsrccompileBADLY_NAMED_CODE.java:16: 警告: 名称 NOTcamelCASEmethodNAME 应该以小写字母开头
        public void NOTcamelCASEmethodNAME() { 
                    ^
    10 个警告
    0
     

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

    参考《Java编译器》

  • 相关阅读:
    RTP 控制协议
    非关系型数据库
    关系型数据库
    处理海量数据
    处理大并发
    C++ 模板偏特化-来自STL的思考
    C++详解new/delete
    二分算法来相会
    计算机网络知识点总结
    C++字符串类型转换
  • 原文地址:https://www.cnblogs.com/duanxz/p/13360361.html
Copyright © 2011-2022 走看看