Javac 编译自定义注解及分析 Lombok 的注解实现

一、用Lombok引出问题

1.1、引入

1、idea 中打开 settings (快捷键:ctrl+alt+s) ,搜索 plugin ,在 plugins 里面搜索 lombok ,安装

2、在项目中引入 lombok 的依赖

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
</dependency>
复制代码

1.2、优缺

Lombok 是一个 Java 库,能自动插入编辑器并构建工具,简化 Java 开发。通过添加注解的方式,不需要为类编写 getter或 eques 方法,同时可以自动化日志变量。官网链接

优点:简化 Java 开发,减少了许多重复代码。

缺点

  • 降低了源码的可读性和完整性;
  • 有可能会破坏封装性,因为有些属性并不需要向外暴露;
  • 降低了可调试性;
  • Lombok 会帮我们自动生成很多代码,但这些代码是在编译期生成的,因此在开发和调试阶段这些代码可能是“丢失的”,这就给调试代码带来了很大的不便。

如果不考虑的那么严谨,我觉得还是要用的,因为我懒。

1.3、使用

写一个类来分析一下:

我们自己手写的一个JavaBean

/**
 * @description:
 * @author: Yihui Wang
 * @date: 2022年07月06日 20:23
 */

public class Student {

    private String name;

    private String age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
}
复制代码

用了 lombok 注解的 JavaBean

/**
 * @description:
 * @author: Yihui Wang
 * @date: 2022年07月06日 20:23
 */
@Data
public class StudentLombok {
    private String name;
    private String age;
}
复制代码

我们编译一下,Idea 中点击顶部菜单 Build ,下拉选择 Recompile 看看他们生成的 class文件是什么样的。

可以明显看出,使用了 @Setter、@Getter 注解后,和我们手动编写的 Java 代码,编译完的结果是一样的。

它直接帮我们生成了这些方法,这些步骤究竟是谁做的勒?我们是否也可以自己编写这样的注解呢?

二、Lombok 原理分析

其实这里面用到了 AOP 编程的编译时织入技术,就是在编译的时候修改最终 class 文件。

大部分的程序代码从开始编译到最终转化成物理机的目标代码或虚拟机能执行的指令集之前,都会按照如下图所示的各个步骤进行:

Javac 的编译过程

归纳起来主要是由以下三个过程组成:

  • 分析和输入到符号表
  • 注解处理
  • 语义分析和生成 class 文件

而Lombok 正是利用注解处理这一步来进行实现的。Lombok 使用的是 JDK 6 实现的 JSR 269: Pluggable Annotation Processing API (编译期的注解处理器) ,它允许在编译期处理注解,读取、修改、添加抽象语法树中的内容。

其实说到这里,我们还只是知道它是在这一步处理的,但如何处理的,我们还是一无所知。

稍后我们会手动实现 Lombok 中的 @Getter、@Setter 注解,这里先事先说明可能会牵扯到的知识。

  • 主要使用到的都是 jdk 源码的 tools.ja 包
  • 使用的 api 主要是com.sun.tools.javac包下的
  • 抽象语法 JCTree 使用

不懂也没关系,我也不是很懂,哈哈,我也只是因为好奇,才来探寻的

其中最主要的就是牵扯到的AbstractProcessor抽象注解处理类,还有就是 JCTree 相关的api,这些的话,我也用的不多,不敢胡乱发言。

要实现注解处理器首先要做的就是继承抽象类 javax.annotation.processing.AbstractProcessor,然后重写它的 process() 方法,process() 方法是 javac 编译器在执行注解处理器代码时要执行的过程。

/**
一个抽象注释处理器,旨在成为大多数具体注释处理器的方便超类。
 */
public abstract class AbstractProcessor implements Processor {
    /**
     * Processing environment providing by the tool framework.
     */
    protected ProcessingEnvironment processingEnv;
    private boolean initialized = false;


    /**
   如果处理器类使用SupportedOptions进行注释,则返回一个不可修改的集合,该集合与注释的字符串集相同。
   如果类没有这样注释,则返回一个空集
     */
    public Set<String> getSupportedOptions() {
        SupportedOptions so = this.getClass().getAnnotation(SupportedOptions.class);
        if  (so == null)
            return Collections.emptySet();
        else
            return arrayToSet(so.value());
    }

    /**
如果处理器类使用SupportedAnnotationTypes进行注释,则返回一个不可修改的集合,
该集合具有与注释相同的字符串集。如果类没有这样注释,则返回一个空集。
return:
此处理器支持的注释类型的名称,如果没有则为空集
     */
    public Set<String> getSupportedAnnotationTypes() {
            SupportedAnnotationTypes sat = this.getClass().getAnnotation(SupportedAnnotationTypes.class);
            if  (sat == null) {
                if (isInitialized())
                    processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
                                                             "No SupportedAnnotationTypes annotation " +
                                                             "found on " + this.getClass().getName() +
                                                             ", returning an empty set.");
                return Collections.emptySet();
            }
            else
                return arrayToSet(sat.value());
        }

    /**
如果处理器类使用SupportedSourceVersion进行注解,则在注解中返回源版本。
如果类没有这样注释,则返回SourceVersion.RELEASE_6 
     */
    public SourceVersion getSupportedSourceVersion() {
        SupportedSourceVersion ssv = this.getClass().getAnnotation(SupportedSourceVersion.class);
        SourceVersion sv = null;
        if (ssv == null) {
            sv = SourceVersion.RELEASE_6;
            if (isInitialized())
                processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING,
                                                         "No SupportedSourceVersion annotation " +
                                                         "found on " + this.getClass().getName() +
                                                         ", returning " + sv + ".");
        } else
            sv = ssv.value();
        return sv;
    }

    /**
该方法有两个参数,“annotations” 表示此处理器所要处理的注解集合;
“roundEnv” 表示当前这个 Round 中的语法树节点,
每个语法树节点都表示一个 Element(javax.lang.model.element.ElementKind 可以查看到相关 Element)。
     */
    public abstract boolean process(Set<? extends TypeElement> annotations,
                                    RoundEnvironment roundEnv);

  
}
复制代码

另外还有这两个用来配合的 注解:

@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
复制代码

@SupportedAnnotationTypes 表示注解处理器对哪些注解感兴趣,“*” 表示对所有的注解都感兴趣;@SupportedSourceVersion 指出这个注解处理器可以处理最高哪个版本的 Java 代码。

三、简易版 Lombok 实现

简要说明

先说说我们要实现的东西,为了简单的去理解,我这里只讨论get、set 方法,其实里面的实现都差不多,如果偏要说不同的话,就是调用的javac的api不同吧。

写了两个注解 :@MyGetter@MySetter 和他们的处理器 MyAnnotationProcessor

注解处理器,顾名思义就是用来处理注解的啦。

项目结构:

由于是maven项目,这里面引用了com.sun.tools的东西,所以,需要在maven的pom文件里面加上,这样,在使用maven打包的时候,才不会报错。

<dependency>
    <groupId>com.sun</groupId>
    <artifactId>tools</artifactId>
    <version>1.8</version>
    <scope>system</scope>
    <systemPath>jdk路径/lib/tools.jar</systemPath>
 </dependency>
复制代码

我们这里利用 Java SPI 加载自定义注解器的方式,生成一个 jar 包,类似于 Lombok ,这样之后其它应用一旦引用了这个 jar 包,自定义注解器就能自动生效了。

SPI是java提供的一种服务发现的标准,具体请看SPI介绍,但每次我们都需要自己创建services目录,以及配置文件,google的autoservice就可以帮我们省去这一步。

    <dependency>
        <groupId>com.google.auto.service</groupId>
        <artifactId>auto-service</artifactId>
        <version>1.0-rc5</version>
    </dependency>
复制代码

如果你使用Processor(javax.annotation.processing.Processor),并且你的元数据文件被包含在了一个jar包中,同时这个jar包是在javac(java编译)的classpath路径下时,javac会自动的执行通过该方式注入进去的Processor的实现类,以实现对于该项目内的相关数据的扩展。

使用 AutoService 会自动的生成META-INF./services/javax.annotation.processing.Processor 文件,并且文件中内容就是我们动态注入进去的类。

然后在编译的时候就会执行对应的扩展方法,同时写入文件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 中自定义注解接口的实现是为了提供一种元数据的形式,用于在编译时或运行时对代码进行标记和定制。以下是如何创建和使用自定义注解的步骤: 1. **定义注解接口**: 创建一个新的注解接口,通常使用 `@interface` 关键字,例如: ```java @Retention(RetentionPolicy.RUNTIME) public @interface MyCustomAnnotation { String value() default ""; int param() default 0; boolean flag() default false; } ``` 这里定义了一个名为 `MyCustomAnnotation` 的注解,它有三个属性:`value`、`param` 和 `flag`。 2. **在类上应用注解**: 在类、方法、变量等上使用 `@MyCustomAnnotation` 标记,例如: ```java @MyCustomAnnotation(value = "customValue", param = 123, flag = true) public class MyClass { // ... } ``` 3. **处理注解**: - 如果在编译期间(`RetentionPolicy.SOURCE` 或 ` RetentionPolicy.CLASS`),可以使用 `javac` 编译器插件访问注解信息。 - 如果在运行期间(`RetentionPolicy.RUNTIME`),可以使用反射 API(如 `java.lang.reflect.Annotation`)获取和处理注解。 4. **编写注解处理器**(可选): 如果你想在编译期间做更多的处理,可以创建一个注解处理器类,实现 `javax.annotation.processing.Processor` 接口。这个处理器会在编译时被调用,处理你定义注解。 相关问题: 1. 如何查看Java源码中的注解信息? 2. 注解处理器的主要用途是什么? 3. 如何在运行时动态获取类上的自定义注解

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值