深入理解Java虚拟机 插入式注解处理器实战总结

本文介绍了一款基于Javac编译器的注解处理器——NameCheckProcessor,它能在编译阶段检查Java程序的命名是否符合驼式命名法等规范。通过实战演示了如何编写并运行该工具。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

实战目标
通过阅读Javac编译器的源码 我们知道编译器在把Java程序源码编译为字节码的时候 会对Java程序源码做各方面的检查校验 这些校验主要以程序 写得对不对 为出发点 虽然也有各种WARNING的信息 但总体来讲还是较少去校验程序 写得好不好 我们将会使用注解处理器API来编写一款拥有自己编码风格的校验工具:NameCheckProcessor
NameCheckProcessor的目标也仅定为对Java程序命名进行检查 Java程序命名应当符合下列格式的书写规范
类(或接口):符合驼式命名法 首字母大写
方法:符合驼式命名法 首字母小写
字段:
●类或实例变量:符合驼式命名法 首字母小写
●常量:要求全部由大写字母或下划线构成 并且第一个字符不能是下划线上文提到的驼式命名法(Camel Case Name) 正如它的名称所表示的那样 是指混合使用大小写字母来分割构成变量或函数的名字 犹如驼峰一般 这是当前Java语言中主流的命名规范 我们的实战目标就是为Javac编译器添加一个额外的功能 在编译程序时检查程序名是否符合上述对类(或接口)、方法、字段的命名要求

代码实现
注解处理器NameCheckProcessor

import java.util.Set;

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;

// 可以用"*"表示支持所有Annotations
@SupportedAnnotationTypes("*")
// 只支持JDK 1.7的Java代码
@SupportedSourceVersion(value = SourceVersion.RELEASE_7)
public class NameCheckProcessor extends AbstractProcessor {

    private NameChecker nameChecker;

    /**
     * 初始化名称检查插件
     */
    @Override
    public void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        nameChecker = new NameChecker(processingEnv);
    }

    /**
     * 对输入的语法树的各个节点进行进行名称检查
     */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (!roundEnv.processingOver()) {
            for (Element element : roundEnv.getRootElements())
                nameChecker.checkNames(element);
        }
        return false;
    }

}

命名检查器NameChecker

import java.util.EnumSet;

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.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementScanner7;
import javax.tools.Diagnostic.Kind;

/**
 * 程序名称规范的编译器插件:<br>
 * 如果程序命名不合规范,将会输出一个编译器的WARNING信息
 */
public class NameChecker {
    private final Messager messager;

    NameCheckScanner nameCheckScanner = new NameCheckScanner();

    NameChecker(ProcessingEnvironment processsingEnv) {
        this.messager = processsingEnv.getMessager();
    }

    /**
     * 对Java程序命名进行检查,根据《Java语言规范》第三版第6.8节的要求,Java程序命名应当符合下列格式:
     * 
     * <ul>
     * <li>类或接口:符合驼式命名法,首字母大写。
     * <li>方法:符合驼式命名法,首字母小写。
     * <li>字段:
     * <ul>
     * <li>类、实例变量: 符合驼式命名法,首字母小写。
     * <li>常量: 要求全部大写。
     * </ul>
     * </ul>
     */
    public void checkNames(Element element) {
        nameCheckScanner.scan(element);
    }

    /**
     * 名称检查器实现类,继承了JDK 1.7中新提供的ElementScanner7<br>
     * 将会以Visitor模式访问抽象语法树中的元素
     */
    private class NameCheckScanner extends ElementScanner7<Void, Void> {

        /**
         * 此方法用于检查Java类
         */
        @Override
        public Void visitType(TypeElement e, Void p) {
            scan(e.getTypeParameters(), p);
            checkCamelCase(e, true);
            super.visitType(e, p);
            return null;
        }

        /**
         * 检查方法命名是否合法
         */
        @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(Kind.WARNING, "一个普通方法 “" + name + "”不应当与类名重复,避免与构造函数产生混淆", e);
                checkCamelCase(e, false);
            }
            super.visitExecutable(e, p);
            return null;
        }

        /**
         * 检查变量命名是否合法
         */
        @Override
        public Void visitVariable(VariableElement e, Void p) {
            // 如果这个Variable是枚举或常量,则按大写命名检查,否则按照驼式命名法规则检查
            if (e.getKind() == ElementKind.ENUM_CONSTANT || e.getConstantValue() != null || heuristicallyConstant(e))
                checkAllCaps(e);
            else
                checkCamelCase(e, false);
            return null;
        }

        /**
         * 判断一个变量是否是常量
         */
        private boolean heuristicallyConstant(VariableElement e) {
            if (e.getEnclosingElement().getKind() == ElementKind.INTERFACE)
                return true;
            else if (e.getKind() == ElementKind.FIELD && e.getModifiers().containsAll(EnumSet.of(javax.lang.model.element.Modifier.PUBLIC, javax.lang.model.element.Modifier.STATIC, javax.lang.model.element.Modifier.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(Kind.WARNING, "名称“" + name + "”应当以小写字母开头", e);
                    return;
                }
            } else if (Character.isLowerCase(firstCodePoint)) {
                if (initialCaps) {
                    messager.printMessage(Kind.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(Kind.WARNING, "名称“" + name + "”应当符合驼式命名法(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(Kind.WARNING, "常量“" + name + "”应当全部以大写字母或下划线命名,并且以字母开头", e);
        }
    }
}

包含了多处不规范命名的代码样例

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


运行与测试
我们可以通过Javac命令的“-processor”参数来执行编译时需要附带的注解处理器 如果有多个注解处理器的话 用逗号分隔 还可以使用-XprintRounds和-XprintProcessorInfo参数来查看注解处理器运作的详细信息 本次实战中的NameCheckProcessor的执行过程如下
NameChecker.java
NameCheckProcessor.java
javac -processor NameCheckProcessor BADLY_NAMED_CODE.java

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值