我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

利用Java手写一个简易的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;


@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内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

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

Lombok是一款Java开发插件,使得Java开发者可以通过其定义的一系列注解来消除业务工程中冗长和繁琐的代码,尤其对于简单的Java模型对象。本文就来手写一个简易的lombok,需要的可以参考一下
2022-11-13

Java实现手写一个线程池的示例代码

线程池技术想必大家都不陌生把,相信在平时的工作中没有少用,而且这也是面试频率非常高的一个知识点,那么大家知道它的实现原理和细节吗?本文就来通过手写一个简单的线程池框架,去掌握线程池的基本原理,感兴趣的可以学习一下
2022-11-13

编写简易Android天气应用的代码示例

本文所要介绍的简易天气App主要用RxAndroid、MVP、Retrofit实现,首先来看看效果: 主页内容:右侧栏天气列表:左侧栏城市列表首先看看Activity主要代码(使用MVP模式)://调用Presenter的方法获取数据 m
2022-06-06

java实现一个简单的网络爬虫代码示例

目前市面上流行的爬虫以python居多,简单了解之后,觉得简单的一些页面的爬虫,主要就是去解析目标页面(html)。那么就在想,java有没有用户方便解析html页面呢?找到了一个jsoup包,一个非常方便解析html的工具呢。使用方式也非
2023-05-30

利用Java编写一个简单的租车系统

这期内容当中小编将会给大家带来有关利用Java编写一个简单的租车系统,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。实现目标java编写一个控制台版的“租车系统”实现功能 1.展示所有可租车辆
2023-05-31

利用java编写一个简单的音乐播放器

今天就跟大家聊聊有关利用java编写一个简单的音乐播放器,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。具体内容如下源码:package baidu;import java.awt.*
2023-05-31

详解android写一个选择图片的示例代码

可以达到的效果第一个图片的位置放照相机,点击打开照相机其余的是显示全部存储的图片,点击一次是查看大图,长按则是每张图片出现一个checkBox,可以进行选择 下面是实例效果图 MainActivity 类public class MainA
2022-06-06

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

在这篇文章当中我们主要介绍实现一个非常简易版的线程池,深入的去理解其中的原理,麻雀虽小,五脏俱全,感兴趣的小伙伴快跟随小编一起学习学习吧
2022-11-13

Java利用Redis实现消息队列的示例代码

本文介绍了Java利用Redis实现消息队列的示例代码,分享给大家,具体如下:应用场景为什么要用redis二进制存储、java序列化传输、IO连接数高、连接频繁一、序列化这里编写了一个java序列化的工具,主要是将对象转化为byte数组,和
2023-05-31

Java使用lambda表达式简化代码的示例详解

这篇文章主要给大家介绍了Java如何使用lambda表达式简化代码的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2022-11-13

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录