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

news2024/11/15 17:55:57

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

    • 一、前言
    • 二、Java 规范提案
    • 三、注解分类
    • 四、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";
    }


}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/97229.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

人人都能看懂的Spring原理,看完绝对不会懵逼

人人都能看懂的Spring原理&#xff0c;绝对不会懵逼为什么要使用Spring&#xff1f;Spring的核心组件Spring是如何实现IOC和DI的&#xff1f;定义了BeanDefinition扫描加载BeanDefinition根据BeanDefinition进行Bean的实例化和初始化实例化属性赋值保存到单例缓冲池一个Bean从创…

【Java开发】 Staffjoy 01 :项目目标及案例需求

Staffjoy 是 Spring Boot & Kubernetes 云原生微服务实践&#xff0c;是一个贴近生产的微服务云原生教学案例&#xff0c;本文依波波老师的课程。新开了一个坑&#xff0c;希望能搞懂微服务框架&#xff0c;感兴趣的同学也欢迎讨论~ 目录 1 Staffjoy 项目目标 2 Staffjoy…

【Java寒假打卡】Java基础-StringBuilder类

【Java寒假打卡】Java基础-StringBuilder类一、概述二、构造方法三、常用方法四、StringBuilder提高效率的原理五、StringBuilder实现字符串反转一、概述 也就是使用String 拼接字符串每一次都要开辟新的堆内存空间&#xff0c;使用StringBuilder不需要开辟新的内存空间 String…

软件架构设计的七大原则

学习设计原则是学习设计模式的基础。千万不能形成强迫症。当碰到业务复杂的场景时&#xff0c;需要随机应变。 在实际开发过程中&#xff0c;并不是一定要求所有代码都遵循设计原则&#xff0c;而是要在适当的场景遵循设计原则&#xff0c;就可以帮助开发者设计出更加优雅的代…

glibc内存管理那些事儿

Linux内存空间简介 32位Linux平台下进程虚拟地址空间分布如下图: 进程虚拟地址空间分布 图中&#xff0c;0xC0000000开始的最高1G空间是内核地址空间&#xff0c;剩下3G空间是用户态空间。用户态空间从上到下依次为stack栈(向下增长)、mmap(匿名文件映射区)、Heap堆(向上增长…

网络编程之IO多路复用

目录 一. 同步与阻塞 1.1 同步阻塞 1.2 同步非阻塞 1.3 异步阻塞 1.4 异步非阻塞 1.5 I/O多路 二.多路复用的技术 2.1 UNIX I/O Models 2.1.1 blocking I/O 2.1.2 nonblocking I/O 2.1.3 I/O Multiplexing Model 2.1.4 SIGIO 2.1.5 asynchronous I/O 2.2 IO多路复…

【拓扑排序】课程表问题

一、拓扑排序问题描述 给定&#xff1a;一系列任务 &#xff08;A&#xff0c;B&#xff0c;C …&#xff09; 任务间的依赖关系 (B 和 C 必须在 A 之前完成&#xff0c; …) 输出&#xff1a;这些任务间的合法执行顺序 &#xff08;C – B – A – …&#xff09; 总之&am…

「图文讲解」浏览器原理与页面渲染过程

相信大家在面试的时候面试官总是会问一个问题&#xff1a;“可以说下浏览器从输入url到渲染页面完成过程吗&#xff1f;”&#xff0c;非官方标准答案来啦&#xff5e; 那我们首先需要先来讲一下浏览器是什么&#xff0c;浏览器其实就是一个应用软件&#xff0c;在学习操作系统…

《数据结构、算法与应用C++语言描述》线性表-数组描述

《数据结构、算法与应用C语言描述》线性表-数组描述 5表示在 数据结构算法与应用C语言描述 书中所在章节。 本文包含了《数据结构、算法与应用C语言描述》第五章主要练习题答案&#xff0c;给出了线性表数组描述完整测试代码。 5.1 数据对象 5.1.1 定义 数据对象(data obj…

java开发的师生评教小程序学生对老师评价老师对班级评价打分题单选题意见框系统选课系统

简介 源码1.0&#xff08;源码2.0选课功能&#xff0c;请往下看&#xff09; 师生评教小程序&#xff0c;学生可以对老师进行评价&#xff0c;老师可以对班级行进评级。管理员可以创建不同的评教模板&#xff08;单选题0分或者10分&#xff0c;打分题0-10分&#xff0c;意见框…

三分钟彻底搞懂paint,repaint,update!

最近总结了一下java中的paint&#xff0c;repaint和updata三者之间的关系&#xff0c;首先咱们都知道用paint方法来绘图&#xff0c;用repaint重绘&#xff0c;用update来写双缓冲。但是他们之间是怎么来调用的呢&#xff0c;咱们来分析一下(想直接看结果&#xff0c;请跳过分析…

2022卡塔尔世界杯感想

一、概述 说说我个人吧&#xff01;在体育活动上面真是没什么兴趣&#xff0c;篮球&#xff0c;足球...等等竞技运动不敏感&#xff01; 今年稍微关注了点世界杯比赛&#xff01;什么原因呢&#xff1f;我有一个爱买彩票/赌球的一个同事&#xff01;随着世界杯的进行&#xf…

风力发电机组机械功率Pm与转子转速Wm关系(Matlab实现)

目录 1 数学模型 2 代码 3 结果 1 数学模型 风力机空气动力学模型 风力涡轮机的动态输出机械扭矩表示为: 其中是空气密度 &#xff0c; A是叶片扫掠面积 &#xff0c; R是风力涡轮机的转子半径 (m)&#xff0c; 是风速 (m/s)。是叶片的功率系数&#xff0c;它是叶片桨距…

linux内网渗透:docker逃逸

Docker逃逸 前言 Docker 逃逸在渗透测试中面向的场景大概是这样&#xff0c;渗透拿到shell后&#xff0c;发现主机是docker环境&#xff0c;要进一步渗透&#xff0c;就必须逃逸到“直接宿主机”。甚至还有物理机运行虚拟机&#xff0c;虚拟机运行Docker容器的情况。那就还要…

基于VUE.JS的招聘系统

开发工具(eclipse/idea/vscode等)&#xff1a;idea 数据库(sqlite/mysql/sqlserver等)&#xff1a;mysql 功能模块(请用文字描述&#xff0c;至少200字)&#xff1a;本文是基于Vue.js对招聘系统的设计与实现&#xff0c;对招聘系统管理员、个人用户、企业用户三个模块功能的完善…

kubernetes学习之路--污点容忍度横向主节点

参考&#xff1a;K8s污点容忍度横向主节点-安全客 - 安全资讯平台 一.kube-scheduler调度 kube-scheduler是Kubernetes 集群的默认调度器&#xff0c;并且是集群控制面(master)的一部分。对每一个新创建的Pod或者是未被调度的Pod&#xff0c;kube-scheduler会选择一个最优的N…

[附源码]Nodejs计算机毕业设计基于图书管理系统Express(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分…

43_SDIO基础知识

目录 SDIO协议简介 SDIO设备分类 SD卡物理结构 SD卡寄存器列表 SDIO总线拓扑 SDIO总线 SDIO总线协议 SDIO命令 命令格式 命令类型 响应 SD卡的操作模式 卡识别模式 数据传输模式 STM32 SDIO功能框图 命令通道 命令状态机 数据通道 数据状态机 数据FIFO SDI…

自定义java注解案例

今天与大家分享java注解的基本使用&#xff0c;如有哪里有问题&#xff0c;望大家指教。 目录 1. 什么是java注解 2. java注解的分类 2.1 JDK基本注解 2.2 JDK元注解 3. 自定义注解 3.1 自定义注解语法 4. 注解示例 4.1 定义注解 4.2 拦截器 4.3 使用注解 4.4 测试 …

Kali Linux渗透测试演示——DNS欺骗

目录 一、DNS欺骗 1.介绍 2.原理 二、环境和工具 1.kali Linux和靶机&#xff08;这里选择windows 7&#xff09; 2.ettercap 三、攻击过程 1.首先确认一下Kali和靶机的IP地址以及网关 2.进入ettercap的配置文件 3.打开ettercap&#xff0c;进行主机发现 总结 一、DNS…