利用Java手写一个简易的lombok的示例代码

目录
  • 1.概述
  • 2.lombok使用方法
  • 3.lombok原理解析
  • 4.手写简易lombok

1.概述

在面向对象编程中,必不可少的需要在代码中定义对象模型,而在基于Java的业务平台开发实践中尤其如此。相信大家在平时开发中也深有感触,本来是没有多少代码开发量的,但是因为定义的业务模型对象比较多,而需要重复写Getter/Setter、构造器方法、字符串输出的ToString方法、Equals/HashCode方法等。我们都知道Lombok能够替大家完成这些繁琐的操作,但是其背后的原理很少有人会关注或者说得清,本文会带着大家了解这一开发神器内部的运行机制与原理!

Lombok是一款Java开发插件,使得Java开发者可以通过其定义的一系列注解来消除业务工程中冗长和繁琐的代码,尤其对于简单的Java模型对象(POJO)。在开发环境中使用Lombok插件后,Java开发人员可以节省出重复构建,诸如HashCode和Equals这样的方法以及各种业务对象模型的accessor和ToString等方法的大量时间。对于这些方法,它能够在编译源代码期间自动帮我们生成这些方法,且并不会如反射那样降低程序的性能。主要是这样比较灵活,即使你在实体类中新增了属性,也不用重新回过头来维护该实体的set和get方法等。

2.lombok使用方法

安装插件,在编译类路径中加入lombok.jar包(具体安装方法可自己百度);

在需要简化的类或方法上,加上要使用的注解;

使用支持lombok的编译工具编译源代码(关于支持lombok的编译工具,见4.支持lombok的编译工具);

编译得到的字节码文件中自动生成Lombok注解对应的方法或代码;

3.lombok原理解析

接下来,我们进行lombok的原理分析,以Oracle的javac编译工具为例。自Java 6起,javac开始支持JSR 269 Pluggable Annotation Processing API规范,只要程序实现了该API,就能在java源码编译时调用定义的注解。举例来说,现在有一个实现了"JSR 269 API"的程序A,那么使用javac编译源码的时候具体流程如下:

javac对源代码进行分析,生成一棵抽象语法树(AST);

运行过程中调用实现了"JSR 269 API"的A程序;

此时A程序就可以完成它自己的逻辑,包括修改第一步骤得到的抽象语法树(AST);

javac使用修改后的抽象语法树(AST)生成字节码文件;

详细的流程图如下:

从上面的Lombok执行的流程图中可以看出,在Javac 解析成AST抽象语法树之后, Lombok 根据自己编写的注解处理器,动态地修改 AST,增加新的节点(即Lombok自定义注解所需要生成的代码),最终通过分析生成JVM可执行的字节码Class文件。使用Annotation Processing自定义注解是在编译阶段进行修改,而jdk的反射技术是在运行时动态修改,两者相比,反射虽然更加灵活一些但是带来的性能损耗更加大。

Lombok本质上就是一个实现了JSR 269 API的程序,在使用javac的命令过程中,它生效的具体流程如下:

  • javac对源代码进行分析,生成一棵抽象语法树(AST);
  • 运行过程中调用实现了JSR 269 API的lombok程序;
  • 编译机会调用lombok程序对第一步得到的AST进行处理,找到其注解所在类对应的语法树(AST),然后修改该语法树,增加注解对应的方法或代码片段到定义的相应树节点;
  • javac使用修改后的抽象语法树生成最终的java字节码文件;

4.手写简易lombok

使用的是idea工具进行开发,使用的jdk版本为1.8,因为我们是自己手写的idea提示会报错,但是能正常运行,因为lombok是idea针对于他有插件提示,我们的没有,但是也不影响正常使用。

1.我们需要使用到jdk安装路径下lib包下的tools.jar,我们可以收到加入到项目依赖,也可以在maven中直接引入。我们直接使用idea新建一个普通的maven项目,然后配置如下,最后将这个项目打包一下,在别的项目中引入即可。

maven配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>lombok</groupId>
    <artifactId>com.compass.lombok</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>com.sun</groupId>
            <artifactId>tools</artifactId>
            <version>1.8</version>
            <scope>system</scope>
            <systemPath>C:/Program Files/Java/jdk1.8.0_251/lib/tools.jar</systemPath>
        </dependency>
        <dependency>
            <groupId>com.google.auto.service</groupId>
            <artifactId>auto-service</artifactId>
            <version>1.0-rc5</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

还有一个是 com.google.auto.service 这个是使用SPI机制的一个依赖,关于spi可以自行百度了解,这里就不再进行展开。

关键核心接口:AbstractProcessor,这个就是在编译期处理注解的一个接口,然后我们可以通过实现这个接口通过修改字节码文件,最终在字节码文件中生成get和set方法。

首先我们定义一个DATA注解:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Data {
}

然后写一个 DataAnnotationProcessor 继承AbstractProcessor即可

import com.compass.lombok.annotation.Data;
import com.google.auto.service.AutoService;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Type;
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.tree.TreeTranslator;
import com.sun.tools.javac.util.*;

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;

/**
 * @author compass
 * @date 2022-10-13
 * @since 1.0
 **/
@AutoService(Processor.class)
@SupportedAnnotationTypes("com.compass.lombok.annotation.Data")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class DataAnnotationProcessor extends AbstractProcessor {
    private JavacTrees javacTrees;
    private TreeMaker treeMaker;
    private Names names;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
         javacTrees = JavacTrees.instance(context);
         treeMaker = TreeMaker.instance(context);
         names = Names.instance(context);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(Data.class);
        for (Element element : set) {
            javacTrees.getTree(element).accept(new TreeTranslator(){
                @Override
                public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
                    jcClassDecl.defs.stream()
                            .filter(it->it.getKind().equals(Tree.Kind.VARIABLE))
                            .map(it->(JCTree.JCVariableDecl) it).forEach(it->{
                                jcClassDecl.defs = jcClassDecl.defs.prepend(genGetterMethod(it));
                                jcClassDecl.defs = jcClassDecl.defs.prepend(genSetterMethod(it));

                    });
                    super.visitClassDef(jcClassDecl);
                }
            });
        }
        return true;
    }

    private JCTree.JCMethodDecl genGetterMethod(JCTree.JCVariableDecl jcVariableDecl){
        JCTree.JCIdent _this = treeMaker.Ident(names.fromString("this"));
        Name name = jcVariableDecl.getName();
        JCTree.JCFieldAccess select = treeMaker.Select(_this, name);
        JCTree.JCReturn returnStatement = treeMaker.Return(select);

        ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
        statements.append(returnStatement);

        JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);

        Name getMethodName = getGetMethodName(jcVariableDecl.getName());

        JCTree.JCExpression returnMethodType = jcVariableDecl.vartype;

        JCTree.JCBlock body = treeMaker.Block(0, statements.toList());

        List<JCTree.JCTypeParameter> methodGenericParamList = List.nil();

        List<JCTree.JCVariableDecl> parameterList = List.nil();

        List<JCTree.JCExpression> throwList = List.nil();

        return treeMaker.MethodDef(modifiers, getMethodName, returnMethodType, methodGenericParamList, parameterList, throwList, body, null);

    }
    public  JCTree.JCMethodDecl genSetterMethod(JCTree.JCVariableDecl jcVariableDecl){

        JCTree.JCIdent _this = treeMaker.Ident(names.fromString("this"));
        Name name = jcVariableDecl.getName();
        JCTree.JCFieldAccess select = treeMaker.Select(_this, name);
        JCTree.JCAssign statementAssign = treeMaker.Assign(select, treeMaker.Ident(jcVariableDecl.getName()));
        JCTree.JCExpressionStatement statement = treeMaker.Exec(statementAssign);
        ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
        statements.append(statement);

        JCTree.JCVariableDecl params = treeMaker.VarDef(
                treeMaker.Modifiers(Flags.PARAMETER, List.nil()),
                jcVariableDecl.name,
                jcVariableDecl.vartype,
                null
        );

        JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);

        Name setMethodName = getSetMethodName(jcVariableDecl.getName());

        JCTree.JCExpression returnMethodType = treeMaker.Type(new Type.JCVoidType());

        JCTree.JCBlock body = treeMaker.Block(0, statements.toList());

        List<JCTree.JCTypeParameter> methodGenericParamList = List.nil();

        List<JCTree.JCVariableDecl> parameterList = List.of(params);

        List<JCTree.JCExpression> throwList = List.nil();

        return treeMaker.MethodDef(modifiers, setMethodName, returnMethodType, methodGenericParamList, parameterList, throwList, body, null);
    }

    private Name getGetMethodName(Name name){
        String filedName = name.toString();
        return names.fromString("get"+filedName.substring(0,1).toUpperCase()+filedName.substring(1));
    }

    private Name getSetMethodName(Name name){
        String filedName = name.toString();
        return names.fromString("set"+filedName.substring(0,1).toUpperCase()+filedName.substring(1));
    }

}

其实到这里就编写完毕了,这里去动态修改字节码,然后生成了get和set方法,至于其他的方法那就后面再说,此案例参照于《深入jvm字节码》进行编写。

最后在maven项目中打包

在别的项目直接使用即可,直接在别的项目的实体类上加上@Data注解即可生成get和set方法,但是没有方法提升,但是能正常运行,这里是idea的一个代码提示的问题,因为我们这个没有对应的idea插件,所以idea会提示报错,但是能正常运行。

到此这篇关于利用Java手写一个简易的lombok的示例代码的文章就介绍到这了,更多相关Java手写lombok内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java Lombok简介、使用、工作原理、优缺点

    简介 官方介绍 Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java. Never write another getter or equals method again, with one annotation your class has a fully featured builder, automate your lo

  • Java开发神器Lombok安装与使用详解

    目录 安装 Idea插件的安装 maven中pom文件的导入 使用 val @Data @Getter/@Setter @Log4j @AllArgsConstructor @NoArgsConstructor @EqualsAndHashCode @NonNull @RequiredArgsConstructor @Cleanup @ToString @Value @SneakyThrows @Synchronized @Builder 安装 Lombok的安装分两部分:Idea插件的安装和m

  • Java效率工具之Lombok的具体使用

    还在编写无聊枯燥又难以维护的POJO吗?洁癖者的春天在哪里?请看Lombok! 在过往的Java项目中,充斥着太多不友好的代码:POJO的getter/setter/toString:异常处理:I/O流的关闭操作等等,这些样板代码既没有技术含量,又影响着代码的美观,Lombok应运而生. 首先说明一下:任何技术的出现都是为了解决某一类问题的,如果在此基础上再建立奇技淫巧,不如回归Java本身.应该保持合理使用而不滥用. Lombok的使用非常简单,下面我们一起来看下: 1)引入相应的maven包

  • 提升java开发效率工具lombok使用争议

    目录 引言 什么是 lombok 如何使用 lombok 的原理和滥用 引言 对使用 lombok 还是有很多争议的,有些公司不建议使用,有些公司又大量使用. 我们的想法是:可以使用,但是不要滥用. 什么是 lombok Lombok是 一种Java™实用工具,可用来帮助开发人员消除Java的冗长代码,尤其是对于简单的Java对象(POJO) . 它通过注释实现这一目的. 通过在开发环境中实现Lombok,开发人员可以节省构建诸如hashCode() 和 equals()这样的方法以及以往用来分

  • Java枚举类使用Lombok方式

    目录 Java枚举类使用Lombok 普通写法 Lombok写法 更精简的写法 Java自定义枚举类 枚举类的创建 枚举类的使用 Java枚举类使用Lombok 枚举类是一个特殊的常量类,由于其特殊的设计,具有简洁性.安全性以及便捷性,在开发中被普遍使用. 本文简单介绍一下如何使用Lombok进行枚举类定义. 按照阿里巴巴的规范,所有的枚举类型字段必须要有注释,说明每个数据项的用途. 这里为了节省篇幅就省略了. 普通写法 public enum BoolEnum {     TRUE(1), F

  • Java开发神器Lombok使用详解

    最近正在写SpringBoot系列文章和录制视频教程,每次都要重复写一些Getter/Setter.构造器方法.字符串输出的ToString方法和Equals/HashCode方法等.甚是浪费时间,也影响代码的可读性.因此,今天就给大家推荐一款Java开发神器--Lombok,让代码更简单易读. 什么是Lombok Lombok是一款Java开发插件,可以通过它定义的注解来精简冗长和繁琐的代码,主要针对简单的Java模型对象(POJO). 好处就显而易见了,可以节省大量重复工作,特别是当POJO

  • 利用Java手写一个简易的lombok的示例代码

    目录 1.概述 2.lombok使用方法 3.lombok原理解析 4.手写简易lombok 1.概述 在面向对象编程中,必不可少的需要在代码中定义对象模型,而在基于Java的业务平台开发实践中尤其如此.相信大家在平时开发中也深有感触,本来是没有多少代码开发量的,但是因为定义的业务模型对象比较多,而需要重复写Getter/Setter.构造器方法.字符串输出的ToString方法.Equals/HashCode方法等.我们都知道Lombok能够替大家完成这些繁琐的操作,但是其背后的原理很少有人会

  • 利用Java手写阻塞队列的示例代码

    目录 前言 需求分析 阻塞队列实现原理 线程阻塞和唤醒 数组循环使用 代码实现 成员变量定义 构造函数 put函数 offer函数 add函数 take函数 重写toString函数 完整代码 总结 前言 在我们平时编程的时候一个很重要的工具就是容器,在本篇文章当中主要给大家介绍阻塞队列的原理,并且在了解原理之后自己动手实现一个低配版的阻塞队列. 需求分析 在前面的两篇文章ArrayDeque(JDK双端队列)源码深度剖析和深入剖析(JDK)ArrayQueue源码当中我们仔细介绍了队列的原理,

  • 如何手写一个简易的 Vuex

    前言 本文适合使用过 Vuex 的人阅读,来了解下怎么自己实现一个 Vuex. 基本骨架 这是本项目的src/store/index.js文件,看看一般 vuex 的使用 import Vue from 'vue' import Vuex from './myvuex' // 引入自己写的 vuex import * as getters from './getters' import * as actions from './actions' import state from './stat

  • 模仿Spring手写一个简易的IOC

    这个小项目是我读过一点Spring的源码后,模仿Spring的IOC写的一个简易的IOC,当然Spring的在天上,我写的在马里亚纳海沟,哈哈 感兴趣的小伙伴可以去我的github拉取代码看着玩 地址: https://github.com/zhuchangwu/CIOC 项目中有两种方式实现IOC: 第一种是基于dom4j实现的解析XML配置文件版 第二种是基于自定义注解实现全配置版 全注解版 模仿Spring原生的IOC机制如下: Interface类型的beanDefinition不会被实

  • 基于SpringCloud手写一个简易版Sentinel

    Sentinel 是什么? 随着微服务的流行,服务和服务之间的稳定性变得越来越重要.Sentinel 以流量为切入点,从流量控制.熔断降级.系统负载保护等多个维度保护服务的稳定性. 不可否认的是,Sentinel功能丰富,并且在提供好用的dashboard提供配置,但是Sentinel在集成到项目中时需要引入多个依赖,并且需要阅读相关文档,以及dashboard中的相关配置才可以接入到项目中,这个过程还是较为复杂的. 如果我们的项目并不需要这么多的功能,只是需要当某个方法或者某个功能发生异常的时

  • 如何利用Java AWT 创建一个简易计算器

    目录 一.关于AWT 二.逻辑部分 1.对于数字按钮 2.对于算术按钮 3.对于等号按钮 4.对于清除按钮 5.对于退格按钮 6.特殊插件功能 7.==例如==: 三.GIF演示 四.附完整代码 摘要:手把手教你使用 Java AWT 创建一个简易计算器. 一.关于AWT AWT (抽象窗口工具包)是一个有助于构建 GUI 的 API (图形用户界面)基于 java 应用程序.GUI使用一些图形帮助用户交互.它主要由一组的类和方法所必需的,如在一个简化的方式创建和管理的GUI按钮,窗口,框架,文

  • 基于Java手写一个好用的FTP操作工具类

    目录 前言 windows服务器搭建FTP服务 工具类方法 代码展示 使用示例 前言 网上百度了很多FTP的java 工具类,发现文章代码都比较久远,且代码臃肿,即使搜到了代码写的还可以的,封装的常用操作方法不全面,于是自己花了半天实现一个好用的工具类.最初想用java自带的FTPClient 的jar 去封装,后来和apache的jar工具包对比后,发现易用性远不如apache,于是决定采用apache的ftp的jar 封装ftp操作类. windows服务器搭建FTP服务 打开控制版面,图示

  • Java实现手写乞丐版线程池的示例代码

    目录 前言 线程池的具体实现 线程池实现思路 线程池实现代码 线程池测试代码 杂谈 总结 前言 在上篇文章线程池的前世今生当中我们介绍了实现线程池的原理,在这篇文章当中我们主要介绍实现一个非常简易版的线程池,深入的去理解其中的原理,麻雀虽小,五脏俱全. 线程池的具体实现 线程池实现思路 任务保存到哪里? 在上篇文章线程池的前世今生当中我们具体去介绍了线程池当中的原理.在线程池当中我们有很多个线程不断的从任务池(用户在使用线程池的时候不断的使用execute方法将任务添加到线程池当中)里面去拿任务

  • Python利用tkinter实现一个简易番茄钟的示例代码

    之前捣鼓树莓派时,要求做一个番茄钟,但最后就只是搞成一个与树莓派没啥关系的py程序,虽然简陋,但就此记录一下自学的成果. 程序实现番茄工作法:25分钟工作,5分钟休息 完成一次番茄工作时间,就记一个番茄 (不把休息时间算在里面,有时候自己都不想休息,好吧,是我不知道怎么把番茄工作时间和休息时间联系在一块来记录番茄个数) 这个程序倒计时显示的是从24:59开始,是因为按的时候算是1秒? 运行界面如下: 自己感觉这个界面还行,朴素中带着点高级感 代码参考了一些大佬写的番茄钟程序,特别是那个倒计时的实

  • 如何从零开始利用js手写一个Promise库详解

    前言 ECMAScript 是 JavaScript 语言的国际标准,JavaScript 是 ECMAScript 的实现.ES6 的目标,是使得 JavaScript 语言可以用来编写大型的复杂的应用程序,成为企业级开发语言. 概念 ES6 原生提供了 Promise 对象. 所谓 Promise,就是一个对象,用来传递异步操作的消息.它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的 API,可供进一步处理. 三道思考题 刚开始写前端的时候,处理异步请求经常用

随机推荐