javac 编译期拓展之实现 CallSuper 注解功能

news2024/11/16 3:50:57

javac 编译期拓展之 实现 CallSuper 注解功能


背景:

元旦之前,就和朋友探讨了这么一个问题。比如我在一个父类的 a 方法里做了一些逻辑,这个逻辑是必须存在的,假如现在子类要重写这个 a 方法,
那么他就需要先调用父类的 a 方法。如果他不调用那么就应该编译报错。

解决这个问题其实有两种方法:

  1. 将这个问题绕过去,比如将父类的 a 方法直接设置为final 不让子类重写,然后再定义一个额外的方法 b 让子类重写。
    在b 之前父类的必要逻辑已经做了
public class Parent{
    final void a(){
        //做必要的操作,然后调用b
        b();
    }
    
    void b(){

    }
}

这样的好处是简单。不好的地方是,如果是一些很通用的方法。大家都约定俗成的用的 a 方法,就你这必须得要我重写 b 方法,就破坏了程序一致性,也需要一些额外的文档和沟通成本。

  1. 第二种方法那就直面问题,比如我在父类的方法上加上一个注解,这个注解就表示你必须在重写的时候先调用父类的方法。比如安卓里面就有 CallSuper 注解。子类重写时没调用父类方法编译就通不过。
  • 从简单解决问题来讲,我更推荐方法1,能跑就行了

  • 如果要优雅的解决问题,我觉得应该使用方法2。就像 CallSuper 注解一样,既能保证程序的一致性也能保证正确性

平时的一些知识积累,以及后面不断捣鼓,勉强实现了方法2 这么个功能,当然也只是简单实现了,问题还有不少。

查项目代码

接下来简单介绍一下,如何实现一个 javac 编译期间校验 CallSuper 注解的程序

功能介绍: 实现一个程序,提供一个注解 MustCallSuper 注解,其它项目的父类方法加上了此注解后,如果子类重写了此方法但是没在最开始调用super.这个方法,就编译报错。

如下编译就应该报错:


public class Parent{

   @MustCallSuper
   protected void test(String name, int age, Integer lo){

   }
} 

public class Son extends Parent {

   @Override
   protected void test(String name, int age, Integer lo) {
       //super.test(name, age, lo); 没调用父类方法编译报错
       System.out.println(123);
   }
}

基础条件: 可以基于java自带的 注解处理器(Annotation Processing Tool),在此拓展功能

其它:为了使用方便使用,创建一个maven项目,然后打包,写入 spi 等信息,借助 spi 自动完成 注解处理器调用。

问题注意:当前只支持标准的maven 项目 src/main/java 这种目录结构的项目,只支持 java8

开发步骤简介:

  1. 创建 MustCallSuper 注解和 注解处理器
  2. 在注解处理器中,拿到当前类的父类的信息,然后遍历父类的方法,找到有 MustCallSuper 注解的方法。(如果需要支持多层继承的情况,可以递归一直向上找,直到找到java.lang.Object)
  3. 遍历当前类的方法,判断是否有重写1中的有 MustCallSuper 注解的方法,如果没有,流程结束,如果有找到重写的方法
  4. 利用 javaparser 解析源码,对重写的方法进行判断,判断第一行是否是super.这个方法。
  5. 校验不通过,抛异常

抽象实现(简单介绍下过程,具体细节可以查看项目源码):

  1. 新建一个项目,创建 MustCallSuper 注解

/**
 * @author rxf113
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MustCallSuper {

}

  1. 创建注解处理器,继承 javax.annotation.processing.AbstractProcessor 重写 process 方法,这个方法包含了所有步骤,具体代码看源码。

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

            for (Element rootElement : roundEnv.getRootElements()) {
                TypeElement typeElement = (TypeElement) rootElement;
                //1. 获取父类的有自定义CallSuper注解的方法
                List<ExecutableElement> methodWithCallSuperList = getSupClassMethodsWithCusAnnotation(typeElement);

                if (!methodWithCallSuperList.isEmpty()) {
                    //2. 获取当前类重写了父类方法的当前类方法
                    List<ExecutableElement> overrideMethodBySuperMethod = getOverrideMethodBySuperMethod(methodWithCallSuperList, typeElement);

                    if (!overrideMethodBySuperMethod.isEmpty()) {

                        String classSourceCode = getClassSourceCode(typeElement);

                        for (ExecutableElement executableElement : overrideMethodBySuperMethod) {

                            //3. 判断源码中, 方法第一行是否有调用 super.xxxx
                            boolean b = checkFirstStatementCallSuper(classSourceCode, executableElement);

                            if (!b) {
                                //4. 校验不通过
                                throw new MustCallSuperException("class: " + typeElement.getQualifiedName().toString()
                                        + " method: " + executableElement.getSimpleName().toString()
                                        + " 第一行没调用父类的此方法");
                            }
                        }
                    }
                }
            }
        }
        return false;
    }


  1. 加入 spi 文件,方便在 javac 的时候,自动执行此 processor

在这里插入图片描述

  1. 打包项目:先清空 spi文件javax.annotation.processing.Processor 中的内容,然后 mvn compile编译,然后写入
    com.rxf113.MustCallSuperProcessor 再 mvn package install 。(这里一定得先清空,编译完了再写入内容再打包)

  2. 测试,新建一个maven项目,引入依赖,然后写两个类,发现编译是可以正常判断的。


public class Parent{

    @MustCallSuper
    protected void test(String name, int age, Integer lo){

    }
}

public class Son extends Parent {

    @Override
    protected void test(String name, int age, Integer lo) {
        //super.test(name, age, lo);
        System.out.println(123);
    }
}

MustCallSuper annotation process exception, msg: class: test.reader.Son method: test 第一行没调用父类的此方法

但是对于引入了 maven-compile-plugin 的项目,没生效

项目地址: callSuper

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

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

相关文章

docker(一):基本组成与常用命令

文章目录1. docker基本组成1.1 镜像(image)1.2 容器(container)1.3 仓库(repository)2. docker常用命令2.1 启动类命令2.2 镜像命令2.3 容器命令1. docker基本组成 1.1 镜像(image) docker镜像(image)就是一个只读的模板。镜像可以用来创建docker容器&#xff0c;一个镜像可以…

中职组网络安全2023年山东省省赛Linux 系统渗透提权

B-3:Linux 系统渗透提权 任务环境说明: 服务器场景:Server2204(关闭链接) 用户名:hacker 密码:123456 使用渗透机对服务器信息收集,并将服务器中 SSH 服务端口号作为 flag 提 交;Flag:2283/tcp 使用渗透机对服务器信息收集,并将服务器中主机名称作为 flag 提交;F…

通过keepalived实现高可用

192.168.184.128 主/heartbeat1 192.168.184.129 从/heartbeat2 192.168.184.131 漂移地址 主备基础&#xff1a;需要在128和129服务器上&#xff0c;搭建mysql主从复制 环境基础配置 128、129操作关闭防火墙 # sed -i "s/SELINUXenforcing/SELINUXdisabled/g"…

内卷加速的手机市场,如何寻找新契机?

从此前争相入局的一亿像素摄像头&#xff0c;到不断加码的快充、屏幕刷新率&#xff0c;再到眼下不那么成熟却“硬要上阵”的屏下摄像头技术&#xff0c;原本应该通过技术创新提升用户体验的手机行业&#xff0c;变得越来越内卷&#xff0c;业内人士分析认为手机内卷造成消费者…

【阶段二】Python数据分析Pandas工具使用04篇:数据预处理:数据的汇总

本篇的思维导图: 数据预处理:数据的汇总 数据透视表pivot_table()函数 透视表功能该功能的主要目的就是实现数据的汇总统计。pandas模块中的pivot_table函数就是实现透视表功能的强大函数。 代码 import numpy as

linux解压

linux中主要有.zip&#xff0c;.gz&#xff0c;.bz2及.tar.gz和.tar.bz2等压缩格式 一、.zip&#xff0c;.gz&#xff0c;.bz2格式 .zip格式语法&#xff1a; zip 压缩文件名 源文件 #压缩文件 &#xff08;也能压缩目录&#xff0c;但只会压缩第一个目录&#xff0c;目录中…

牛客网C++项目-Linux高并发服务器开发之第一章:Linux系统编程入门 学习笔记

1.1 Linux 开发环境搭建 由于仅是开发环境的搭建&#xff0c;所以只简单记述一下步骤 必备软件&#xff1a; Ubuntu 18.04 XShell-用于远程登录&#xff0c;使用SSH协议&#xff0c;TCP连接&#xff0c;端口号22 XFtp&#xff0c;本次实验中尚未用到 Visual studio code&a…

什么是蒙特卡洛学习,时序差分算法

在学习的过程中经常会看到蒙卡特洛和时序差分算法&#xff0c;到底这两个是指什么&#xff0c;今天稍微整理下&#xff0c;开始吧。蒙卡特洛1.1 蒙卡特洛方法蒙特卡罗方法又叫做统计模拟方法&#xff0c;它使用随机数(或伪随机数)来解决计算问题。比如上图&#xff0c;矩形的面…

Python全栈开发(二)——python基础语法(二)

我们昨天说了python的数据类型&#xff0c;今天说说python的缩进规则和函数、python的顺序语句结构&#xff0c;条件和分支语句以及循环语句。缩进不规范会报错&#xff08;IndentationError: unexpected indent&#xff09;&#xff0c;python的函数使用&#xff0c;从定义到实…

操作系统——进程之处理机调度

操作系统——进程之处理机调度一、处理机调度的本概念和层次1、高级调度&#xff08;作业调度&#xff09;2、中级调度&#xff08;内存调度&#xff09;3、低级调度&#xff08;进程调度&#xff09;二、进程调度的时机、切换与过程、方式1、进程调度的时机2、进程调度的方式3…

ATAC-seq分析:比对(3)

1. 质控 在比对之前&#xff0c;我们建议花一些时间查看 FASTQ 文件。一些基本的 QC 检查可以帮助我们了解您的测序是否存在任何偏差&#xff0c;例如读取质量的意外下降或非随机 GC 内容。 2. Greenleaf 在本节中&#xff0c;我们将稍微处理一下 Greenleaf 数据集。 我们将处理…

新一代OPC UA解决方案,快速实现IT与OT融合

一、OPC数据采集难题 OPC技术在现今的工业自动化中应用越来越广泛&#xff0c;为现场工业控制设备与控制软件之间的数据交换提供了统一的数据存储规范。但随着工业的不断发展&#xff0c;OPC数据采集出现了一些难题。例如&#xff0c;在传统OPC在远程连接时候一定会面临的DCOM…

Qt扫盲-Qt Designer配置QSS交互使用

Qt Designer配置QSS交互记录一、概述二、用法1. 选择2. 修改1. 菜单区2. 编辑区3. 在底部功能区4. 查询一、概述 Qt Designer {Qt Designer }是一个很好的工具来预览样式表、设置样式的效果&#xff0c;而且是所见即所得&#xff0c;用界面这种开发更快些。 我一般是在Qt Des…

【编译基础】new delete详解及内存泄漏

内存的使用&#xff0c;一文不太够 文章目录C语言1.new关键字2.delete关键字C语言1.malloc关键字2.free关键字区别内存泄漏参考博客&#x1f60a;点此到文末惊喜↩︎ C语言 1.new关键字 作用&#xff1a;C通过new关键字动态分配内存三种用法 plain new&#xff1a;最朴素的n…

JdbcUtils工具类的优化升级——通过配置文件连接mysql8.0

我之前的博文JDBC重构——JdbcUtils工具类的封装写了一个JdbcUtils的工具类&#xff0c;但是这个类也会有一个问题&#xff1a;如下图所示&#xff1a;连接数据库的代码在java中是写死的&#xff0c;如果我们想要换一个数据库进行连接&#xff0c;就会很麻烦&#xff0c;这时我…

嵌入式HLS 案例开发手册——基于Zynq-7010/20工业开发板(2)

目 录 2 led_flash 案例 19 2.1 HLS 工程说明 19 2.2 编译与仿真 20 2.3 IP 核测试 23 3 key_led_demo 案例 23 3.1 HLS 工程说明 23 3.2 编译与仿真 25 3.3 IP 核测试 27 前 言 本文主要介绍 HLS 案例的使用说明,适用开发环境: Windows 7/10 64bit、Xilinx Vivado…

从零搭建的前后端完整的直播网页方案

前言&#xff1a;由于前段时间刚租了台服务器打算自己玩玩&#xff0c;随想首页或者哪哪个页面挂个我个人的直播间应该还挺有趣的。遂探索如何在我的网站上弄一个直播。三下五除二&#xff0c;清清爽爽&#xff0c;看完此文5分钟即可直播。 整体思路 最简单直观的图解。 由上图…

VB2019创建、使用静态库(同样的使用动态库dll)

库&#xff1a; 二进制可执行文件&#xff0c;操作系统载入内存执行&#xff0c;将不怎么更改的底层打包成库后可以使整体编译更改&#xff0c;并且实现对底层的保密&#xff08;不对外或员工开放&#xff09;。库有两种&#xff1a;静态库&#xff08;.a、.lib&#xff09;和动…

【国科大模式识别】第二次作业(阉割版)

【题目一】最大似然估计也可以用来估计先验概率。假设样本是连续独立地从自然状态 ωi\omega_iωi​ 中抽取的, 每一个自然状态的概率为 P(ωi)P\left(\omega_i\right)P(ωi​) 。如果第 kkk 个样本的自然状态为 ωi\omega_iωi​, 那么就记 zik1z_{i k}1zik​1, 否则 zik0z_{i…

【无标题】测试新发文章

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注…