lombok简介
lombok是一个java库,致力于通过一组注解消除代码中的一些必要但是臃肿的样板代码,精简代码,提高效率,还有耍酷。
如何使用
使用lombok需要在IDE中引入对应的插件,并在项目中引入对应的pom依赖
安装插件
在IDEA的插件中搜索lombok然后安装
引入依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
注解
lombok提供了一系列的注解来帮助我们简化代码,下面我们分别对其中一些高频的注解怎么使用进行介绍
@Getter / @Setter
@Getter和@Setter注解可以作用在类上,也可以作用在字段上。
使用前我们的写法如下:
//code1
public class Person {
private Long personId;
private String misNum;
private String fullName;
private String name;
public Long getPersonId() {
return personId;
}
public void setPersonId(Long personId) {
this.personId = personId;
}
public String getMisNum() {
return misNum;
}
public void setMisNum(String misNum) {
this.misNum = misNum;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
使用后我们的写法如下:
//code2
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Person {
private Long personId;
private String misNum;
private String fullName;
private String name;
}
查看编译后的class文件,在编译后会自动生成对应的get和set方法
//code2.class
public class Person {
private Long personId;
private String misNum;
private String fullName;
private String name;
public Person() {
}
public Long getPersonId() {
return this.personId;
}
public String getMisNum() {
return this.misNum;
}
public String getFullName() {
return this.fullName;
}
public String getName() {
return this.name;
}
public void setPersonId(Long personId) {
this.personId = personId;
}
public void setMisNum(String misNum) {
this.misNum = misNum;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public void setName(String name) {
this.name = name;
}
}
@ToString
使用前我们的写法如下:
//code3
public class Person {
private Long personId;
private String misNum;
private String fullName;
private String name;
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("{");
sb.append("\"personId\":")
.append(personId);
sb.append(",\"misNum\":\"")
.append(misNum).append('\"');
sb.append(",\"fullName\":\"")
.append(fullName).append('\"');
sb.append(",\"name\":\"")
.append(name).append('\"');
sb.append('}');
return sb.toString();
}
}
使用后我们的写法如下:
//code4
import lombok.ToString;
@ToString
public class Person {
private Long personId;
private String misNum;
private String fullName;
private String name;
}
编译后的class文件如下,会自动生成toString()方法,但是比较遗憾的是不够灵活,没有办法直接生成Json格式的toString() 方法
//code4.class
public class Person {
private Long personId;
private String misNum;
private String fullName;
private String name;
public Person() {
}
public String toString() {
return "Person(personId=" + this.personId + ", misNum=" + this.misNum + ", fullName=" + this.fullName + ", name=" + this.name + ")";
}
}
@ToString还提供了一些参数
callSuper = true 可以打印父类,指定exclude可以排除字段,不过都比较鸡肋。
@Data
使用@Data写法如下:
//code5
@Data
public class Person {
private Long personId;
private String misNum;
private String fullName;
private String name;
}
编译后的class文件如下:
//code5.class
public class Person {
private Long personId;
private String misNum;
private String fullName;
private String name;
public Person() {
}
public Long getPersonId() {
return this.personId;
}
public String getMisNum() {
return this.misNum;
}
public String getFullName() {
return this.fullName;
}
public String getName() {
return this.name;
}
public void setPersonId(Long personId) {
this.personId = personId;
}
public void setMisNum(String misNum) {
this.misNum = misNum;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public void setName(String name) {
this.name = name;
}
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof Person)) {
return false;
} else {
Person other = (Person)o;
if (!other.canEqual(this)) {
return false;
} else {
label59: {
Object this$personId = this.getPersonId();
Object other$personId = other.getPersonId();
if (this$personId == null) {
if (other$personId == null) {
break label59;
}
} else if (this$personId.equals(other$personId)) {
break label59;
}
return false;
}
Object this$misNum = this.getMisNum();
Object other$misNum = other.getMisNum();
if (this$misNum == null) {
if (other$misNum != null) {
return false;
}
} else if (!this$misNum.equals(other$misNum)) {
return false;
}
Object this$fullName = this.getFullName();
Object other$fullName = other.getFullName();
if (this$fullName == null) {
if (other$fullName != null) {
return false;
}
} else if (!this$fullName.equals(other$fullName)) {
return false;
}
Object this$name = this.getName();
Object other$name = other.getName();
if (this$name == null) {
if (other$name != null) {
return false;
}
} else if (!this$name.equals(other$name)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(Object other) {
return other instanceof Person;
}
public int hashCode() {
int PRIME = true;
int result = 1;
Object $personId = this.getPersonId();
int result = result * 59 + ($personId == null ? 43 : $personId.hashCode());
Object $misNum = this.getMisNum();
result = result * 59 + ($misNum == null ? 43 : $misNum.hashCode());
Object $fullName = this.getFullName();
result = result * 59 + ($fullName == null ? 43 : $fullName.hashCode());
Object $name = this.getName();
result = result * 59 + ($name == null ? 43 : $name.hashCode());
return result;
}
public String toString() {
return "Person(personId=" + this.getPersonId() + ", misNum=" + this.getMisNum() + ", fullName=" + this.getFullName() + ", name=" + this.getName() + ")";
}
}
@Data注解自动生成了getter/setter方法、toString()方法、覆写了hashCode()和equals()方法.
@Slf4j
使用@Slf4j注解可以省去实例化log对象的代码
使用方式如下:
//code6
@Slf4j
public class Person {
}
编译后的class文件如下:
//code6.class
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Person {
private static final Logger log = LoggerFactory.getLogger(Person.class);
public Person() {
}
}
@Builder
@Builder作用在类上可以将类转换为建造者模式
//code7
import lombok.Builder;
@Builder
public class Person {
private Long personId;
private String misNum;
private String fullName;
private String name;
}
编译后的class文件如下
public class Person {
private Long personId;
private String misNum;
private String fullName;
private String name;
Person(Long personId, String misNum, String fullName, String name) {
this.personId = personId;
this.misNum = misNum;
this.fullName = fullName;
this.name = name;
}
public static PersonBuilder builder() {
return new PersonBuilder();
}
}
实例化对象时可以按照下面的方式写:
public class PersonTest {
public static void main(String[] args){
Person person = Person.builder().personId(1L).misNum("zhangsan").build();
}
}
也可以指定默认值:
import lombok.Builder;
import lombok.Singular;
import lombok.ToString;
@Builder
@ToString
public class Person {
private Long personId;
private String misNum;
private String fullName;
private String name;
@Builder.Default
private int tenant = 1;
}
其他的一些方法比较鸡肋 就不一一列出了
原理
lombok的基本流程是:
定义编译期的注解
利用JSR269 api(Pluggable Annotation Processing API )创建编译期的注解处理器
利用tools.jar的javac api处理AST(抽象语法树)
将功能注册进jar包
因为是在编译期生效的 所以其实直接从代码上来看,代码可能都是错误的,所以需要安装对应的IDE 插件对这些错误进行排除
想要了解lombok的原理,肯定是手撸代码来的快
前边提到lombok提供的toString方法不是json格式的,不如我们先来写一个json格式的toString方法 姑且叫做ToJsonString
@ToJsonString
先定义注解类:
package lombok;
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.SOURCE)
public @interface ToJsonString {
}
然后定义对应的处理器:
package lombok;
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.TypeTag;
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 javax.tools.Diagnostic;
import java.util.Set;
@SupportedAnnotationTypes("lombok.ToJsonString")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class ToJsonStringProcessor extends AbstractProcessor {
//主要是用来在编译期打log用的
private Messager messager;
//提供了待处理的抽象语法树
private JavacTrees trees;
//封装了创建AST节点的一些方法
private TreeMaker treeMaker;
//提供了创建标识符的方法
private Names names;
/**
* 从环境里获取一些关键信息
* @param processingEnv
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
this.messager = processingEnv.getMessager();
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) {
//获取被ToJsonString标记的类
Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(ToJsonString.class);
//遍历 生成语法树
set.forEach(element -> {
JCTree jcTree = trees.getTree(element);
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);
}
}
jcClassDecl.defs = jcClassDecl.defs.prepend(makeToJsonStringMethodDecl(jcVariableDeclList));
super.visitClassDef(jcClassDecl);
}
});
});
return false;
}
private JCTree.JCMethodDecl makeToJsonStringMethodDecl(List<JCTree.JCVariableDecl> jcVariableDeclList) {
//方法名
Name toString = names.fromString("toString");
//返回类型
JCTree.JCExpression returnType = getType("java.lang.String");
//泛型参数列表
List<JCTree.JCTypeParameter> methodGenericParams = List.nil();
//参数列表
List<JCTree.JCVariableDecl> params = List.nil();
//异常抛出列表
List<JCTree.JCExpression> throwsClauses = List.nil();
//拼接要输出的JSON
JCTree.JCExpression jcExpression = treeMaker.Literal( "{");
for (int i = 0; i < jcVariableDeclList.size(); i++) {
JCTree.JCVariableDecl jcVariableDecl = jcVariableDeclList.get(i);
if (i != 0){
jcExpression = treeMaker.Binary(JCTree.Tag.PLUS, jcExpression, treeMaker.Literal(",\"" + jcVariableDecl.name.toString() + "\":"));
}else{
jcExpression = treeMaker.Binary(JCTree.Tag.PLUS, jcExpression, treeMaker.Literal("\""+jcVariableDecl.name.toString() + "\":"));
}
if (jcVariableDecl.vartype.toString().contains("String")){
jcExpression = treeMaker.Binary(JCTree.Tag.PLUS, jcExpression, treeMaker.Literal("\""));
}
jcExpression = treeMaker.Binary(JCTree.Tag.PLUS, jcExpression, treeMaker.Ident(jcVariableDecl.name));
if (jcVariableDecl.vartype.toString().contains("String")){
jcExpression = treeMaker.Binary(JCTree.Tag.PLUS, jcExpression, treeMaker.Literal("\""));
}
}
jcExpression = treeMaker.Binary(JCTree.Tag.PLUS, jcExpression, treeMaker.Literal("}"));
JCTree.JCStatement jcStatement = treeMaker.Return(jcExpression);
List<JCTree.JCStatement> jcStatementList = List.nil();
jcStatementList = jcStatementList.append(jcStatement);
//方法体
JCTree.JCBlock body = treeMaker.Block(0, jcStatementList);
return treeMaker.MethodDef(treeMaker.Modifiers(Flags.PUBLIC),toString,returnType,List.nil(), params, List.nil(), body, null);
}
private JCTree.JCExpression getType(String components) {
String[] componentArray = components.split("\\.");
JCTree.JCExpression expr = treeMaker.Ident(names.fromString(componentArray[0]));
for (int i = 1; i < componentArray.length; i++) {
expr = treeMaker.Select(expr, names.fromString(componentArray[i]));
}
return expr;
}
}
再定义一个实体类,并使用@ToJsonString注解
@ToJsonString
public class Person {
private String misNum;
private String fullName;
private Long personId;
}
需要注意下 因为现在不是jar依赖,并且本身是在编译期生效,所以我们需要先编译处理器 再编译Person类,切换到代码的文件目录下,手动编译
//首先创建一个文件夹 存放编译后的class文件
mrdir lombokclass
//对编译器进行编译
javac -cp $JAVA_HOME/lib/tools.jar ToJsonString* -d lombokclass
//编译Person类
javac -cp lombokclass -processor lombok.ToJsonStringProcessor Person.java -d lombokclass
我是直接在IDEA中看的编译后的文件,如下图:
关于JSR269 可以参考网上的这篇文章:JSR269参考资料
争议
正所谓:爱的人爱的疯狂 恨的人恨的发飙
lombok的好处显而易见,代码简洁,看着舒服 扩展时不必重写toString等
但反对使用的人也有很多正当理由
1.入侵性强 耦合增加 胁迫使用
首先如果你在项目里使用了lombok,那同一个项目组的小伙伴都必须安装lombok插件;如果你在对外提供的api里使用了lombok,那引用你的依赖的项目也必须使用lombok,简直是在耍流氓
2.技术债务
lombok自身无法及时支持最高的Java版本 如果升级项目的java版本时,lombok对此并不支持,将需要做很多工作去生成对应的方法
3.代码调试困难
如果我们需要查看某个getter方法在哪些地方被引用 你会发现做不到
4.equals重写
@Data默认对equals进行了重写,很难说这是不是一件好事,如果只使用了@Data,而不使用@EqualsAndHashCode(callSuper=true)的话,会默认是@EqualsAndHashCode(callSuper=false),这时候生成的equals()方法只会比较子类的属性,不会考虑从父类继承的属性。
所以,就我个人来说,不首先使用lombok,如果项目里已经有了 那我就用