前言

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. JAVAEE_Servlet_19_重定向可以解决页面刷新问题(sendRedirect)

    重定向可以解决页面刷新问题(sendRedirect) 在向数据库中添加数据的时候,如果使用转发(getRequestDispatcher),数据插入成功后,转发到提示插入成功页面,在数据插入成功页面 ...

  2. OO Unit1 总结

    OO Unit1 总结 每次作业的思路和技术分析 No.1 一共写了8个类,2个接口,主要的其实只有4个类1个接口 主要接口: PowerFunction就是每一项去掉系数的那一部分,有求导和乘法两个 ...

  3. golang channel的行为

    1. 读nil的channel是永远阻塞的.关闭nil的channel会造成panic. 2. closed channel的行为: (1)向close的channel发消息会panic,关闭一个已经 ...

  4. Thinkphp5之ajax分页实现_paginate()参数详细

    Thinkphp5 做数据搜索需要带关键词分页,如何将查询条件带入到分页中,本文详细介绍Thinkphp5 分页带参数 一.基本使用方法: $list = Db::name('user')->w ...

  5. thinkphp中常用到的sql操作

    1.清空某表数据: $sql = 'truncate table table_name'; Db::execute($sql );

  6. 【转】python SQLAlchemy

    数据库表是一个二维表,包含多行多列. 把一个表的内容用Python的数据结构表示出来的话,可以用一个list表示多行,list的每一个元素是tuple,表示一行记录,比如,包含id和name的user ...

  7. Windows进程间通讯(IPC)----管道

    管道的分类 管道其实际就是一段共享内存,只不过Windows规定需要使用I/O的形式类访问这块共享内存,管道可以分为匿名管道和命名管道. 匿名管道就是没有名字的管道,其支持单向传输数据,如果需要双向传 ...

  8. C++ primer plus读书笔记——第5章 循环和关系表达式

    第5章 循环和关系表达式 1. cout.setf(ios_base::boolalpha); cout << (100 > 3) << endl;将输出true,而不是 ...

  9. [xml模块、hashlib模块、subprocess模块、os与sys模块、configparser模块]

    [xml模块.hashlib模块.subprocess模块.os与sys模块.configparser模块] xml模块 XML:全称 可扩展标记语言,为了能够在不同的平台间继续数据的交换,使交换的数 ...

  10. JS中函数定义和使用顺序

    在js中如果定义函数是先声明函数,如: console.log(hello); //然后再去定义: function hello(){ alert("Hello");} 这种是可以 ...