lombok -- 愛的人愛的瘋狂 恨的人恨的切齒

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,如果項目裏已經有了 那我就用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章