前言

Lombok的出现帮助开发人员在开发工程中消除了大部分冗余代码:繁琐的get、set方法甚至建造者模式。

Lombok的实现方式是什么呢?

新建一个测试类使用Lombok的Getter和Setter注解,通过IDEA进行编译

import lombok.Getter;
import lombok.Setter; @Getter
@Setter
public class UserInfo {
private String userId; private String userName;
}

打开编译后生成的UserInfo.class文件



发现已经生成了get、set方法,由此可以推断出Lombok是在编译期为代码进行了增强,那么在编译期进行增强是如何实现的?

编译阶段

在JDK6提出并通过了JSR-269提案,提案通过了一组被称为“插入式注解处理器”的标准API,可以提前至编译期对代码中的特定注解进行处理, 从而影响到编译器的工作过程。

对于底层的一些实现,普遍会认为实现是像虚拟机一样使用C++实现,对于Java程序员来说并不是特别友好。但是Javac编译器是使用Java实现的更容易上手。

Javac的编译过程大致分为几步

  1. 准备过程:初始化插入式注解处理器
  2. 解析与填充符号表过程 词法、语法分析构建抽象语法树(AST)
  3. 插入式注解处理器的注解处理过程
  4. 分析与字节码生成过程

    语法树变动后会再次解析与填充符号表,语法树没有变动时编译器就不会再对源码字符流操作,而是基于抽象语法树



    综上所述想实现Lombok的效果只需要遵守JSR-269在编译期对AST进行操作即可实现

    当然不止有Lombok通过这种方式实现,例如FindBug、MapStruct等也通过这种方式实现

实现

JCTree

JCTree是AST元素的基类,想实现效果只需要添加JCTree节点即可

JCTree是一个抽象类部分实现



从类名可以猜到是什么节点使用的这里挑几个常用的解释

JCStatement 声明语法树节点

JCBlock 语法块

JCReturn:return语句语法树节点

JCClassDecl:类定义语法树节点

JCVariableDecl:字段/变量定义语法树节点

JCMethodDecl:方法定义语法树节点

JCModifiers:访问标志语法树节点

JCExpression:表达式语法树节点,常见的子类如下

JCAssign:赋值语句语法树节点

JCIdent:标识符语法树节点 例如this

TreeMaker

主要用于生成语法树节点

代码

首先需要注解类,标明作用的范围和作用的时期,两个类分别对应Lombok的Getter、Setter

@Target({ElementType.TYPE}) //加在类上的注解
@Retention(RetentionPolicy.SOURCE) //作用于编译期
public @interface Getter {
}
@Target({ElementType.TYPE}) //加在类上的注解
@Retention(RetentionPolicy.SOURCE) //作用于编译期
public @interface Setter {
}

新建抽象注解处理器需要继承AbstractProcessor,这里使用模板方法模式

public abstract class MyAbstractProcessor extends AbstractProcessor {
//语法树
protected JavacTrees trees; //构建语法树节点
protected TreeMaker treeMaker; //创建标识符的对象
protected Names names; @Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.trees = JavacTrees.instance(processingEnv);
Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
this.treeMaker = TreeMaker.instance(context);
this.names = Names.instance(context);
} @Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//获取注解标识的类
Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(getAnnotation());
//拿到语法树
set.stream().map(element -> trees.getTree(element)).forEach(jcTree -> jcTree.accept(new TreeTranslator() {
//拿到类定义
@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
List<JCTree.JCVariableDecl> jcVariableDeclList = List.nil();
//拿到所有成员变量
for (JCTree tree : jcClassDecl.defs) {
if (tree.getKind().equals(Tree.Kind.VARIABLE)) {
JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) tree;
jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);
}
} jcVariableDeclList.forEach(jcVariableDecl -> {
jcClassDecl.defs = jcClassDecl.defs.prepend(makeMethodDecl(jcVariableDecl));
});
super.visitClassDef(jcClassDecl);
}
}));
return true;
} /**
* 创建方法
* @param jcVariableDecl
* @return
*/
public abstract JCTree.JCMethodDecl makeMethodDecl(JCTree.JCVariableDecl jcVariableDecl); /**
* 获取何种注解
* @return
*/
public abstract Class<? extends Annotation> getAnnotation();
}

用于处理Setter注解 继承MyAbstractProcessor

@SupportedAnnotationTypes("com.ingxx.processor.Setter") //注解处理器作用于哪个注解 也可以重写getSupportedAnnotationTypes
@SupportedSourceVersion(SourceVersion.RELEASE_8) //可以处理什么版本 也可以重写getSupportedSourceVersion
public class SetterProcessor extends MyAbstractProcessor { @Override
public JCTree.JCMethodDecl makeMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
//生成函数体 this.name = name;
statements.append(treeMaker.Exec(treeMaker.Assign(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName()),treeMaker.Ident(jcVariableDecl.getName()))));
JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
//生成方法
return treeMaker.MethodDef(
treeMaker.Modifiers(Flags.PUBLIC), //访问标志
getNewMethodName(jcVariableDecl.getName()), //名字
treeMaker.TypeIdent(TypeTag.VOID), //返回类型
List.nil(), //泛型形参列表
List.of(getParameters(jcVariableDecl)), //参数列表
List.nil(), //异常列表
body, //方法体
null //默认方法(可能是interface中的那个default)
);
} @Override
public Class<? extends Annotation> getAnnotation() {
return Setter.class;
} private Name getNewMethodName(Name name) {
String fieldName = name.toString();
return names.fromString("set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1, name.length()));
} private JCTree.JCVariableDecl getParameters(JCTree.JCVariableDecl prototypeJCVariable) {
return treeMaker.VarDef(
treeMaker.Modifiers(Flags.PARAMETER), //访问标志
prototypeJCVariable.name, //名字
prototypeJCVariable.vartype, //类型
null //初始化语句
);
}
}

用于处理Getter注解 继承MyAbstractProcessor

@SupportedAnnotationTypes("com.ingxx.processor.Getter") //注解处理器作用于哪个注解 也可以重写getSupportedAnnotationTypes
@SupportedSourceVersion(SourceVersion.RELEASE_8) //可以处理什么版本 也可以重写getSupportedSourceVersion
public class GetterProcessor extends MyAbstractProcessor { @Override
public JCTree.JCMethodDecl makeMethodDecl(JCTree.JCVariableDecl jcVariableDecl) {
ListBuffer<JCTree.JCStatement> statements = new ListBuffer<>();
//生成函数体 return this.字段名
statements.append(treeMaker.Return(treeMaker.Select(treeMaker.Ident(names.fromString("this")), jcVariableDecl.getName())));
JCTree.JCBlock body = treeMaker.Block(0, statements.toList());
//生成方法
return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC), getNewMethodName(jcVariableDecl.getName()), jcVariableDecl.vartype, List.nil(), List.nil(), List.nil(), body, null);
} @Override
public Class<? extends Annotation> getAnnotation() {
return Getter.class;
} private Name getNewMethodName(Name name) {
String fieldName = name.toString();
return names.fromString("get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1, name.length()));
}
}

编译现有类

javac -cp $JAVA_HOME/lib/tools.jar *.java -d .

新建一个测试类 IDEA中由于找不到get set方法会报错,可以忽略

@Getter
@Setter
public class UserInfo {
private String userId; private String userName; public static void main(String[] args) {
UserInfo userInfo = new UserInfo();
userInfo.setUserId("001");
userInfo.setUserName("得物");
System.out.println("id = "+userInfo.getUserId()+" name = "+userInfo.getUserName());
}
}

接着编译

//多个处理器用逗号分隔
javac -processor com.ingxx.processor.GetterProcessor,com.ingxx.processor.SetterProcessor UserInfo.java -d .

查看编译后的文件发现已经生成了get、set方法

从Lombok到JSR-269的更多相关文章

  1. Lombok 简单入门

    原文地址:Lombok 简单入门 博客地址:http://www.extlight.com 一.前言 Lombok 是一个 Java 库,它作为插件安装至编辑器中,其作用是通过简单注解来精简代码,以此 ...

  2. Spring Boot 快速入门笔记

    Spirng boot笔记 简介 Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发 ...

  3. Lombok 安装、入门 - 消除冗长的 java 代码(转)

    前言:    逛开源社区的时候无意发现的,用了一段时间,觉得还可以,特此推荐一下.    lombok 提供了简单的注解的形式来帮助我们简化消除一些必须有但显得很臃肿的 java 代码.特别是相对于 ...

  4. Java开发速度神器Lombok,Eclipse端安装使用教程

    一.Lombok简介 Lombok是一个代码生成器,可以通过简单的注解形式来帮助我们简化消除一些必须有但显得很臃肿的Java代码的工具,通过使用对应的注解,可以在编译源码的时候生成对应的方法. 使用 ...

  5. Lombok介绍、使用方法和总结

    1 Lombok背景介绍 官方介绍如下: Project Lombok makes java a spicier language by adding 'handlers' that know how ...

  6. SpringBoot集成Lombok,应用+源码解析,让代码优雅起来

    一.Lombok简介 (1)Lombok官网(https://projectlombok.org/)对lombok的介绍 (2)GitHub项目地址:https://github.com/rzwits ...

  7. Lombok插件看法浅谈

    背景 最近接触的几个工程中Lombok插件出现频率比较高,趁机了解一下原理. 简要说明: 受益于JSR 269 API,程序可以在编译阶段对AST进行节点的操作,从而注入相关的功能结点,从而包含在最终 ...

  8. 基于IDEA工具 lombok 的使用

    一.简介 Lombok 是一种 Java™ 实用工具,可用来帮助开发人员消除 Java 的冗长,尤其是对于简单的 Java 对象(POJO).它通过注解实现这一目的. 二.lombok的添加和常用注解 ...

  9. lombok的简单介绍

    ##lombok的使用 一直在使用lombok的set和get,对其他的功能用的比较少,蓦然发现这个库好用的功能不要太多啊 有必要深入理解一番. ###lombok安装 1 需要IDE支持,不然开发的 ...

  10. 简化Getter 与 Setter 的插件 Lombok

    参考文档:https://www.jianshu.com/p/365ea41b3573 第一步:添加依赖 <dependency> <groupId>org.projectlo ...

随机推荐

  1. 使用Docker及k8s启动logstash服务

    前提:搭建好elasticsearch和kibana服务 下载镜像,需要下载与elasticsearch和kibana相同版本镜像 docker pull docker.elastic.co/logs ...

  2. B - Rikka with Graph HDU - 5631 (并查集+思维)

    As we know, Rikka is poor at math. Yuta is worrying about this situation, so he gives Rikka some mat ...

  3. 【vim】复制粘贴相关操作

    复制: 首先,可以在命令模式下输入v进入自由选取模式,选择需要剪切的文字后,按下d就可以进行剪切了. 其他命令模式下剪切命令: yy:复制当前行 nyy:n表示大于1的数字,复制n行 yw:从光标处复 ...

  4. tp5 composer phpexcel使用方法

    1.compser 安装phpexcel.在windows命令行下输入:进入网站根目录,compser phpoffice/phpexcel 2.页面引入两个类: use PHPExcel_IOFac ...

  5. hdu4503 概率

    题意: 湫湫系列故事--植树节                                         Time Limit: 1000/500 MS (Java/Others) Memory ...

  6. POJ1087DFS+匈牙利或者DINIC

    题意:      有n个插孔,m个电器,q种转换器(转换器可以无限用),然后问你最多有多少个电器能充电. 思路:       比较简单,就是在建图的时候要考虑下,我用了两种方法做的,一个是最大流,这个 ...

  7. Win64 驱动内核编程-24.64位驱动里内嵌汇编

    64位驱动里内嵌汇编 讲道理64位驱动是不能直接内链汇编的,遇到这种问题,可以考虑直接把机器码拷贝到内存里,然后直接执行. 获得机器码的方式,可以写好代码之后,直接通过vs看反汇编,然后根据地址在看内 ...

  8. 『动善时』JMeter基础 — 7、jmeter.properties文件常用配置

    目录 1.默认语言设置 2.配置默认编码格式 3.GUI图标放大比例设置 4.功能区工具栏图标大小设置 5.视图区目录树图标大小设置 6.内容区编辑字体设置 7.添加JMeter元素快捷键设置 8.捕 ...

  9. Method Overlonding

    The method overloading is using one single method name with different parameters to created differen ...

  10. h5实现电子签名

    前端需要引入:jSignature.min.js ,jquery-1.9.1.min.js前端 部分写法:body内加 <div id="signature">< ...