Lombok 的源码仓库
https://github.com/projectlombok/lombok
Lombok 的官网:
https://projectlombok.org/
Project Lombok最初是由Reinier Zwitserloot(在Twitter上被称为@surial)和Roel Spilker在2009年之前创建的。名称“Lombok”从位于Java东部的同名印度尼西亚岛屿汲取灵感。此外,“Lombok”是印尼语中的“辣椒”的意思,与该库的标语“Spicing up your Java”相一致。
/**
* 用户对象
*/
@Getter
@Setter
public class UserPO implements Serializable {
/** 主键ID */
private String id;
/** 本系统用户账号 */
private String account;
/** 显示姓名 */
private String username;
}
https://blog.csdn.net/w1014074794/article/details/128244097
lombok 其实本质上就是在编译 java 源码到字节码的时候,通过识别注解,自动生成了对应的 get 和 set 方法等等。
Java 在编译的时候,还能动态生成生成和修改字节码?它是怎么做的呢?
一切的开始还得从 Java 6 说起,自 Java 6 起 ,javac 指令开始支持 「JSR 269 Pluggable Annotation Processing API」 规范,只要程序实现了该API,就能在java源码编译时调用定义的注解。
具体的执行流程:
javac对源代码进行分析,生成一棵抽象语法树(AST)
运行过程中调用实现了 “JSR 269 API” 的程序
通过实现了 “JSR 269 API” 的程序,操作抽象语法树(AST)
javac使用修改后的抽象语法树(AST)生成字节码文件
简单的实现
3.1 先创建两个简单的工程
工程A:util
工程B:test
工程 B 依赖工程 A
【ps:一定要构建两个项目,不然编译的时候会存在异常:「Javax.annotation.processing.Processor: Provider xxx not found」,因为在编译的时候,javac会去找所有jar包及项目(模块)里 resource/META-INF/services/javax.annotation.processing.Processor 这个文件中配置的类信息,记住是类信息,它会通过classloader去加载这个类,此时如果是在同一个项目(模块)中的文件,因为是在编译期,尚未生成class文件,自然也就找不到对应的类。】
方法1:去掉javax.annotation.processing.Processor文件,等编译完成后,再将文件拷贝到对应的目录去(但每次编译后都需要拷贝)
方法2:去掉javax.annotation.processing.Processor文件,在需要使用编译的项目(模块)添加此文件(推荐,无依赖)
方法3:使用谷歌的autoService注解(推荐)
com.google.auto.service
auto-service
1.0.1
我们这里通过两个工程的方式来解决这个问题,其实就是方法2
3.2 工程 A utils
A. 添加依赖包:
com.sun
tools
1.6.0
system
${java.home}/../lib/tools.jar
B. 构建自定义注解 CustomGetSet
@Retention(RetentionPolicy.SOURCE) // 在源码中有效
@Target(ElementType.TYPE) // 作用于类,接口
public @interface CustomGetSet {
}
C. 构建自定义注解处理器 CustomGetSetProcessor
核心思路就是继承 AbstractProcessor 类,在 process() 方法中操作抽象语法树
public class CustomGetSetProcessor extends AbstractProcessor {
private Messager messager; // 编译时期输入日志的
private JavacTrees javacTrees; // 提供了待处理的抽象语法树
private TreeMaker treeMaker; // 封装了创建AST节点的一些方法
private Names names; // 提供了创建标识符的方法
//支持的注解类型
@Override
public Set getSupportedAnnotationTypes() {
Set annotataions = new LinkedHashSet();
annotataions.add(CustomGetSet.class.getCanonicalName());
return annotataions;
}
//支持的java版本
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.messager = processingEnv.getMessager();
this.javacTrees = 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> elementsAnnotatedWith = roundEnv.getElementsAnnotatedWith(CustomGetSet.class);
elementsAnnotatedWith.forEach(element -> {
// 得到类的抽象树结构
JCTree tree = javacTrees.getTree(element);
tree.accept(new TreeTranslator() {
@Override
public void visitClassDef(JCTree.JCClassDecl jcClassDecl) {
List jcVariableDeclList = List.nil();
// 在抽象树中找出所有的变量
for (JCTree jcTree : jcClassDecl.defs) {
if (jcTree.getKind().equals(Tree.Kind.VARIABLE)) {
JCTree.JCVariableDecl jcVariableDecl = (JCTree.JCVariableDecl) jcTree;
jcVariableDeclList = jcVariableDeclList.append(jcVariableDecl);
}
}
// 对于变量进行生成方法的操作
jcVariableDeclList.forEach(jcVariableDecl -> {
messager.printMessage(Diagnostic.Kind.NOTE, jcVariableDecl.getName() + " has been processed");
jcClassDecl.defs = jcClassDecl.defs.prepend(makeGetterMethodDecl(jcVariableDecl));
});
super.visitClassDef(jcClassDecl);
}
});
});
return true;
}
/**
* 生成 getter 方法
* @param jcVariableDecl
* @return
*/
private JCTree.JCMethodDecl makeGetterMethodDecl(JCTree.JCVariableDecl jcVariableDecl){
ListBuffer statements = new ListBuffer<>();
// 生成表达式 例如 this.a = a;
JCTree.JCExpressionStatement aThis = makeAssignment(treeMaker.Select(treeMaker.Ident(
names.fromString("this")), jcVariableDecl.getName()), treeMaker.Ident(jcVariableDecl.getName()));
statements.append(aThis);
JCTree.JCBlock block = treeMaker.Block(0, statements.toList());
// 生成入参
JCTree.JCVariableDecl param = treeMaker.VarDef(treeMaker.Modifiers(Flags.PARAMETER),
jcVariableDecl.getName(), jcVariableDecl.vartype, null);
List parameters = List.of(param);
// 生成返回对象
JCTree.JCExpression methodType = treeMaker.Type(new Type.JCVoidType());
return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC),
getNewGetterMethodName(jcVariableDecl.getName()), methodType, List.nil(),
parameters, List.nil(), block, null);
}
/**
* 拼装Setter方法名称字符串
* @param name
* @return
*/
private Name getNewSetterMethodName(Name name) {
String s = name.toString();
return names.fromString("set" + s.substring(0,1).toUpperCase() + s.substring(1, name.length()));
}
/**
* 拼装 Getter 方法名称的字符串
* @param name
* @return
*/
private Name getNewGetterMethodName(Name name) {
String s = name.toString();
return names.fromString("get" + s.substring(0,1).toUpperCase() + s.substring(1, name.length()));
}
/**
* 生成表达式
* @param lhs
* @param rhs
* @return
*/
private JCTree.JCExpressionStatement makeAssignment(JCTree.JCExpression lhs, JCTree.JCExpression rhs) {
return treeMaker.Exec(
treeMaker.Assign(lhs, rhs)
);
}
}
3.3 工程 B test
A. 在工程目录下构建一个 resources 文件夹,文件夹下构建 META-INF/services/javax.annotation.processing.Processor 文件,文件内容是你在工程 A 中定义的 CustomGetSetProcessor 自定义处理器的全限定类名。
B. 在工程 B 中,我们定义一个测试类 TestBean 对象,并使用自定义注解 CustomGetSet
@CustomGetSet
public class TestBean {
private String name;
}
3.4 测试编译后的 TestBean 是否生成 get 方法
你可以通过 javac 指令的方式编译,也可以通过 maven 的方式,我因为是以 maven 的方式构建的工程,因此就通过 maven 的方式进行操作
查看编译后的 TestBean 的字节码,的确是生成了 get 方法。至此操作就结束了。当然 Lombok 的实现复杂得多,但是总体的流程和思路就是如此。有兴趣的,大家可以去 github 上学习学习源码。地址小编上面已经给出来了。
「「优点:」」
可用来帮助开发人员消除Java的冗长代码,尤其是对于简单的Java对象(POJO)。
提高效率
简单,通过简单的一些注解就可以实现
「「缺点:」」
代码的可阅读性变差了,调试代码的时候不是很友好。【ps:尤其是在你IDEA没有安装Lombok插件的时候】
插件问题,如果未安装插件,使用IDE打开一个基于Lombok的项目的话会提示找不到方法等错误。导致项目编译失败。所以只要你负责的项目使用了Lombok,就必须安装插件。
不了解实现细节,引起的坑。比如:
当我们使用@Data定义一个类的时候,会自动帮我们生成equals()方法 。
但是如果只使用了@Data,而不使用@EqualsAndHashCode(callSuper=true)的话,
会默认是@EqualsAndHashCode(callSuper=false)
这时候生成的equals()方法只会比较子类的属性,不会考虑从父类继承的属性,无论父类属性访问权限是否开放。
「「建议:」」
基于小编个人的感受来说:
业务项目类:
当我们项目中,在处理大量业务类型的 POJO 对象的时候,还是比较建议使用 Lombok的。因为业务类型的 POJO 的字段是变动还是比较频繁的,如果每次都手动进行生成 get set 这类的方法,太繁琐了,而且容易出错,遗漏和忘记。字段如果很多的话,代码还极其不优雅,效率低下。
核心类,框架类:
如果是对于项目核心的类库,还有就是自身项目的框架,小编还是不太建议使用 Lombok,因为对于核心的类库,一般都有着很强的结构设计和代码设计。Lombok 容易破坏这种结构设计。