前言

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. 07_利用pytorch的nn工具箱实现LeNet网络

    07_利用pytorch的nn工具箱实现LeNet网络 目录 一.引言 二.定义网络 三.损失函数 四.优化器 五.数据加载和预处理 六.Hub模块简介 七.总结 pytorch完整教程目录:http ...

  2. Linux+MicroPython+esp8233 YES!

    MicPython MicroPython是澳大利亚程序员和物理学家Damien George在2013年一次成功的众筹活动后最初创建的.MicroPython 和 CPython 在 Python ...

  3. Day17_103_IO_InputStreamReader 字符转换流

    InputStreamReader 字符转换流 * import java.io.InputStreamReader; 将字节输入流转换为字符输入流 * import java.io.OutputSt ...

  4. 哈工大LTP基本使用-分词、词性标注、依存句法分析、命名实体识别、角色标注

    代码 import os from pprint import pprint from pyltp import Segmentor, Postagger, Parser, NamedEntityRe ...

  5. json 标准库

    1. 序列化的简单概念 2. json 标准库 2.1 json.dumps() 2.2 json.loads() 2.3 json.dump() 2.4 json.load() 1. 序列化的简单概 ...

  6. Ubuntu20.04安装MongoDB

    本教程描述了如何在Ubuntu20.04上安装MongoDB4.4 安装MongoDB Ubuntu 20.04默认存储库中不提供最新版本的MongoDB,因此需要在系统中添加官方的MongoDB存储 ...

  7. css选择器中:first-child 与 :first-of-type的区别

    ## css选择器中:first-child 与 :first-of-type的区别 ---- :first-child选择器是css2中定义的选择器,从字面意思上来看也很好理解,就是第一个子元素.比 ...

  8. tp5 composer phpexcel使用方法

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

  9. SMTP、POP3和IMAP邮件协议

    目录 SMTP POP IMAP 总结 DNS记录中的MX记录 今天入职第一天,公司让配置个人的内网.外网邮箱,这可把我给搞晕了,本来以前就对邮箱这块不是很了解,平时也不怎么用邮箱,顶多有个QQ邮箱而 ...

  10. Windows核心编程 第八章 用户方式中线程的同步(下)

    8.4 关键代码段 关键代码段是指一个小代码段,在代码能够执行前,它必须独占对某些共享资源的访问权.这是让若干行代码能够"以原子操作方式"来使用资源的一种方法.所谓原子操作方式,是 ...