【JVM知识】插入式注解处理器实现java编程规范检测

一、前言

最近在看**《深入理解Java虚拟机》**时,看到了插入式注解处理器,在了解之后发现这个功能在我们平时工作中用到的还是比较广的,虽然我们工作中很少涉及这一块的编码,但是用到的很多组件都是通过这个功能实现的,例如,checkStyleFindBuglombokmapstruct等插件,尤其是lombok插件应该是程序开发必备插件,之前一直没有仔细了解它的实现原理。

二、Java 规范提案

JSRJava Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准

jsr描述相关实现
jsr107缓存规范spring基于此实现的缓存体系
jsr250java平台Common Annontations 如@PostConstructspring
jsr269Pluggable Annotation Processing APIlombok,mapstruct
jsr303,jsr349,jsr380bean validationhibernate-validitor

三、注解分类

注解分为运行时注解编译时期注解

运行时注解:java运行时,JVM中反射技术获取注解信息,执行特定操作;
例如:spring IOC aop mybatis

编译时期注解:java编译器运行时,此时根本没有字节文件,因为编译没有完成;我们按照规范编写一个插件,植入java编译器中;我们修改java源文件对应的抽象语法树,实现特定操作。
例如:lombok mapstruct

区别

  • 保留阶段不同。运行时注解保留到运行时,可在运行时访问。而编译时注解保留到编译时,运行时无法访问。
  • 原理不同。运行时注解是Java反射机制,Retrofit运行时注解,需要用的时候才用到,而编译时注解通过APT、AbstractProcessor。
  • 性能不同。运行时注解由于使用Java反射,因此对性能上有影响。编译时注解对性能没影响。这也是为什么ButterKnife从运行时切换到了编译时的原因。
  • 产物不同。运行时注解只需自定义注解处理器即可,不会产生其他文件。而编译时注解通常会产生新的Java源文件。

四、java编译器

概念javac 是java语言编程编译器。全称java compiler。javac工具读由java语言编写的类和接口的定义,并将它们编译成字节代码的class文件。

javac编译器的工作流程:
在这里插入图片描述
从javac代码的总体结构来看,编译过程大致可以分为1个准备过程3个处理过程,它们分别为:
1)准备过程:初始化插入式注解处理器;
2)解析与填充符号表过程,包括:

  • 词法、语法分析。将将源代码的字符流转变为标记集合,构造出抽象语法树;
  • 填充符号表。产生符号地址和符号信息;

3)插入式注解处理器的注解处理过程:插入式注解处理器的执行阶段;
4)分析与字节码生成过程,包括:

  • 标注检查。对语法的静态信息进行检查;
  • 数据流及控制流分析。对程序动态运行过程进行检查;
  • 解语法糖,将简化代码编写的语法糖还原为原有的形式;
  • 字节码生成。将前面各个步骤所生成的信息转化成字节码。

:执行插入式注解时又可能会产生新的符号,如果有新的符号产生,就必须转回到之前的解析、填充符号表的过程中重新处理这些新符号;

五、插入式注解处理器

1、概念注解处理器(Annotation Processor)是javac内置的一个用于编译时扫描处理注解(Annotation)的工具。简单的说,在源代码编译阶段,通过注解处理器,我们可以获取源文件内注解(Annotation)相关内容javax.annotation.processing.Processor用于替换JDK6之前的APT(Annotatino Processing Tool)

2、使用场景
由于注解处理器可以在程序编译阶段工作,所以我们可以在编译期间通过注解处理器进行我们需要的操作。比较常用的用法就是在编译期间获取相关注解数据,然后动态生成.java源文件(让机器帮我们写代码),通常是自动产生一些有规律性的重复代码,解决了手工编写重复代码的问题,大大提升编码效率

3、javax.annotation.processing.Processor

public interface Processor {
   //返回此 processor 识别的选项。处理工具的实现必须提供一种方式来传递特定于 processor 的选项,这些选项不同于传递给工具自身的选项,
    Set<String> getSupportedOptions();
//返回此 processor 支持的注释类型的名称。结果元素可能是某一受支持注释类型的规范(完全限定)名称。它也可能是 "name.*" 形式的名称,表示所有以 "name." 开头的规范名称的注释类型集合。最后,"*" 自身表示所有注释类型的集合,包括空集。注意,processor 不应声明 "*",除非它实际处理了所有文件;声明不必要的注释可能导致在某些环境中的性能下降。
    Set<String> getSupportedAnnotationTypes();

//用于指定你的java版本,一般返回:SourceVersion.latestSupported()
    SourceVersion getSupportedSourceVersion();

//用处理环境初始化处理器。
    void init(ProcessingEnvironment processingEnv);

   //处理先前 round 产生的类型元素上的注释类型集,并返回这些注释是否由此 processor 声明。如果返回 true,则这些注释已声明并且不要求后续 processor 处理它们;如果返回 false,则这些注释未声明并且可能要求后续 processor 处理它们。processor 可能总是返回相同的 boolean 值,或者可能基于所选择的标准而返回不同的结果。
如果 processor 支持 "*" 并且根元素没有注释,则输入集合将为空。processor 必须妥善处理空注释集。

    boolean process(Set<? extends TypeElement> annotations,
                    RoundEnvironment roundEnv);
quot;),
     
    Iterable<? extends Completion> getCompletions(Element element,
                                                  AnnotationMirror annotation,
                                                  ExecutableElement member,
                                                  String userText);
}

类 AbstractProcessor API说明

六、代码规范检测实现(代码示例)

1、NameChecker类

package com.sk.service.check;

import javax.annotation.processing.Messager;
import javax.lang.model.element.*;
import javax.lang.model.util.ElementScanner6;
import javax.tools.Diagnostic;
import java.util.EnumSet;

public class NameChecker {

    private final Messager messager;

    NameCheckScanner nameCheckScanner = new NameCheckScanner();

    public NameChecker(Messager messager) {
        this.messager = messager;
    }

    public void checkNames(Element element){
        nameCheckScanner.scan(element);
    }

    public class NameCheckScanner extends ElementScanner6<Void,Void> {

        @Override
        public Void visitType(TypeElement e, Void p){
            scan(e.getTypeParameters(),p);
            checkCamelCase(e,true);
            super.visitType(e,p);
            return null;
        }

        public Void visitExecutable(ExecutableElement e, Void p){
            if(e.getKind() == ElementKind.METHOD){
                Name name = e.getSimpleName();
                if(name.contentEquals(e.getEnclosingElement().getSimpleName())){
                    messager.printMessage(Diagnostic.Kind.WARNING,"一个普通方法 "+name+" 不应当与类名重复,避免与构造函数产生混淆",e);
                }
                checkCamelCase(e,false);
            }
            super.visitExecutable(e,p);
            return null;
        }

        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 void checkAllCaps(VariableElement 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(Diagnostic.Kind.WARNING,"常量 "+name+" 应当全部以大写字母或者下划线命令,并且以字母开头",e);
            }
        }

        private boolean heuristicallyConstant(VariableElement e) {

            if(e.getEnclosingElement().getKind() == ElementKind.INTERFACE){
                return true;
            }else if(e.getKind() == ElementKind.FIELD && e.getModifiers().containsAll(EnumSet.of(PUBLIC,STATIC,FINAL))){
                return true;
            }else{
                return false;
            }

        }

        private void checkCamelCase(Element e, boolean b) {

            String name = e.getSimpleName().toString();
            boolean previousUpper = false;
            boolean conventional = true;
            int firstCodePoint = name.codePointAt(0);
            if(Character.isUpperCase(firstCodePoint)){
                previousUpper = true;
                if(!b){
                    messager.printMessage(Diagnostic.Kind.WARNING,"名称 "+name+" 应当以小写字母开头",e);
                }
            }else if(Character.isLowerCase(firstCodePoint)){
                if(b){
                    messager.printMessage(Diagnostic.Kind.WARNING,"名称 "+name+" 应当以大写字母开头",e);
                }
            }else{
                conventional = false;
            }

            if(b){
                int cp = firstCodePoint;
                for(int i=Character.charCount(cp);i<name.length();i+=Character.charCount(cp)) {
                    cp = name.codePointAt(cp);
                    if (Character.isUpperCase(cp)) {
                        if (previousUpper) {
                            conventional = false;
                            break;
                        }
                        previousUpper = true;
                    } else {
                        previousUpper = false;
                    }
                }
            }
            if(!conventional){
                messager.printMessage(Diagnostic.Kind.WARNING,"名称 "+name+" 应当符合驼式命名法",e);
            }

        }


    }

}

2、NameCheckProcessor 类

package com.sk.service.check;

import com.sun.xml.internal.xsom.impl.Ref;

import javax.annotation.processing.*;
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_6)
public class NameCheckProcessor extends AbstractProcessor {

    private NameChecker nameChecker;

    public void init(ProcessingEnvironment processingEnv){
        super.init(processingEnv);
    }


    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        if(!roundEnv.processingOver()){
            for(Element element:roundEnv.getRootElements()){
                nameChecker.checkNames(element);
            }
        }

        return false;
    }
}

七、项目版本统一控制实现(代码示例)

package com.sk.service;

import com.google.auto.service.AutoService;
import com.sk.annotation.BussVersion;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.util.HashSet;
import java.util.Set;

@AutoService(Processor.class)
public class BussVersionProcessor extends AbstractProcessor {

    private JavacTrees javacTrees;
    private TreeMaker treeMaker;

    private ProcessingEnvironment processingEnv;

    /**
     * 初始化处理器
     *
     * @param processingEnv 提供了一系列的实用工具
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.processingEnv = processingEnv;
        this.javacTrees = JavacTrees.instance(processingEnv);
        Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
        this.treeMaker = TreeMaker.instance(context);
    }


    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latest();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        HashSet<String> set = new HashSet<>();
        set.add(BussVersion.class.getName()); // 支持解析的注解
        return set;
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (TypeElement t : annotations) {
            for (Element e : roundEnv.getElementsAnnotatedWith(t)) { // 获取到给定注解的element(element可以是一个类、方法、包等)
                // JCVariableDecl为字段/变量定义语法树节点
                JCTree.JCVariableDecl jcv = (JCTree.JCVariableDecl) javacTrees.getTree(e);
                String varType = jcv.vartype.type.toString();
                if (!"java.lang.String".equals(varType)) { // 限定变量类型必须是String类型,否则抛异常
                    printErrorMessage(e, "Type '" + varType + "'" + " is not support.");
                }
                jcv.init = treeMaker.Literal(getVersion()); // 给这个字段赋值,也就是getVersion的返回值
            }
        }
        return true;
    }

    /**
     * 利用processingEnv内的Messager对象输出一些日志
     *
     * @param e element
     * @param m error message
     */
    private void printErrorMessage(Element e, String m) {
        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, m, e);
    }

    private String getVersion() {
        /**
         * 获取version,这里省略掉复杂的代码,直接返回固定值
         */
        return "v1.0.1";
    }


}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dylan~~~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值