1. 程式人生 > >SpringBoot集成Lombok,讓代碼優雅起來

SpringBoot集成Lombok,讓代碼優雅起來

表示 utf8 exce 哪些 util log4j 駝峰 exc equals

一、Lombok簡介

  技術分享圖片

  (1)Lombok官網(https://projectlombok.org/)對lombok的介紹

  (2)GitHub項目地址:https://github.com/rzwitserloot/lombok

技術分享圖片

  雖然是生硬的翻譯,大家也大致可以看到Lombok存在的價值和意義,Lombok主要是可以提高開發效率,讓我們這些小碼農們工作時可以偷懶,讓我們不再編寫很多臃腫而定式的代碼,雖然現在我們使用IDE工具可以生成很多,但是頻繁的生成也會讓我們的實體類看起來非常的臃腫。Lombok正是我們這些處於水深火熱中的小碼農的福音。

二、Lombok存在的意義:

  (1)簡化冗余的JavaBean代碼,使得實體文件很簡潔;

  (2)便捷的生成比較復雜的代碼,例如一個POJO要轉化成構建器模式的形式,只需要一個註解。

三、Lombok有哪些註解,他們的作用分別是什麽?

  a)Lombok的註解概覽

技術分享圖片

  b)下面對每個註解進行一一總結,如下:

    1、@NotNull

    ①詳細介紹:這個註解可以用在成員方法或者構造方法的參數前面,會自動產生一個關於此參數的非空檢查,如果參數為空,則拋出一個空指針異常。

    ②栗子:

    編譯前:

//成員方法參數加上@NonNull註解
public String getName(@NonNull Person p) {
    return p.getName();
}

    編譯後:

public String getName(@NonNull Person p) {
    if (p == null) {
        throw new NullPointerException("person");
    }
    return p.getName();
}

    2、@Cleanup

    ①、詳細介紹:這個註解用在變量前面,可以保證此變量代表的資源會被自動關閉,默認是調用資源的close()方法,如果該資源有其它關閉方法,可使用@Cleanup("methodName")來指定要調用的方法。

    ②、栗子:

    編譯前:

public static void main(String[] args) throws IOException {
    @Cleanup InputStream in = new FileInputStream(args[0]);
    @Cleanup OutputStream out = new FileOutputStream(args[1]);
    byte[] b = new byte[1024];
    while (true) {
        int r = in.read(b);
        if (r == -1) break;
        out.write(b, 0, r);
    }
 }

    編譯後:

public static void main(String[] args) throws IOException {
    InputStream in = new FileInputStream(args[0]);
    try {
        OutputStream out = new FileOutputStream(args[1]);
        try {
            byte[] b = new byte[10000];
            while (true) {
                int r = in.read(b);
                if (r == -1) break;
                out.write(b, 0, r);
            }
        } finally {
            if (out != null) {
                out.close();
            }
        }
    } finally {
        if (in != null) {
            in.close();
        }
    }
}

    3、@Getter/@Setter

    ①、詳細介紹:這一對註解從名字上就很好理解,用在成員變量前面,相當於為成員變量生成對應的get和set方法,同時還可以為生成的方法指定訪問修飾符,當然,默認為public。也可以用在類上,表示為該類所有的屬性,生成對應的get和set方法。

    ②、栗子:

    編譯前:

public class Programmer {
    @Getter
    @Setter
    private String name;

    @Setter(AccessLevel.PROTECTED)
    private int age;

    @Getter(AccessLevel.PUBLIC)
    private String language;
}

    編譯後:

public class Programmer {
    private String name;
    private int age;
    private String language;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    protected void setAge(int age) {
        this.age = age;
    }

    public String getLanguage() {
        return language;
    }
}

    4、@Getter(lazy=true)

    ①詳細介紹:如果Bean的一個字段的初始化是代價比較高的操作,比如加載大量的數據;同時這個字段並不是必定使用的。那麽使用懶加載機制,可以保證節省資源。懶加載機制,是對象初始化時,該字段並不會真正的初始化,而是第一次訪問該字段時才進行初始化字段的操作

    5、@ToString/@EqualsAndHashCode

    ①詳細介紹:這兩個註解也比較好理解,就是生成toString,equals和hashcode方法,同時後者還會生成一個canEqual方法,用於判斷某個對象是否是當前類的實例,生成方法時只會使用類中的非靜態和非transient成員變量,這些都比較好理解,就不舉例子了。

    當然,這兩個註解也可以添加限制條件,例如用@ToString(exclude={"param1","param2"})來排除param1和param2兩個成員變量,或者用@ToString(of={"param1","param2"})來指定使用param1和param2兩個成員變量,@EqualsAndHashCode註解也有同樣的用法。

    6、@NoArgsConstructor/@RequiredArgsConstructor /@AllArgsConstructor

    ①、詳細介紹:這三個註解都是用在類上的,第一個和第三個都很好理解,就是為該類產生無參的構造方法和包含所有參數的構造方法,第二個註解則使用類中所有帶有@NonNull註解的或者帶有final修飾的成員變量生成對應的構造方法。當然,和前面幾個註解一樣,成員變量都是非靜態的,另外,如果類中含有final修飾的成員變量,是無法使用@NoArgsConstructor註解的。

    三個註解都可以指定生成的構造方法的訪問權限,同時,第二個註解還可以用@RequiredArgsConstructor(staticName="methodName")的形式生成一個指定名稱的靜態方法,返回一個調用相應的構造方法產生的對象。

    ②、栗子:

    編譯前:

@RequiredArgsConstructor(staticName = "sunsfan")
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor
public class Shape {
    private int x;
    @NonNull
    private double y;
    @NonNull
    private String name;
}

    編譯後:

public class Shape {
    private int x;
    private double y;
    private String name;

    public Shape() {
    }

    protected Shape(int x, double y, String name) {
        this.x = x;
        this.y = y;
        this.name = name;
    }

    public Shape(double y, String name) {
        this.y = y;
        this.name = name;
    }

    public static Shape sunsfan(double y, String name) {
        return new Shape(y, name);
    }
}

    7、@Data/@Value

    ①、詳細介紹:@Data註解綜合了@Getter/@Setter,@ToString,@EqualsAndHashCode和@RequiredArgsConstructor註解,其中@RequiredArgsConstructor使用了類中的帶有@NonNull註解的或者final修飾的成員變量,它可以使用@Data(staticConstructor="methodName")來生成一個靜態方法,返回一個調用相應的構造方法產生的對象。

    @Value註解和@Data類似,區別在於它會把所有成員變量默認定義為private final修飾,並且不會生成set方法。

    8、@SneakyThrows

    ①、詳細介紹:這個註解用在方法上,可以將方法中的代碼用try-catch語句包裹起來,捕獲異常並在catch中用Lombok.sneakyThrow(e)把異常拋出,可以使用@SneakyThrows(Exception.class)的形式指定拋出哪種異常。

    ②、栗子:

    編譯前:

public class SneakyThrows implements Runnable {
    @SneakyThrows(UnsupportedEncodingException.class)
    public String utf8ToString(byte[] bytes) {
        return new String(bytes, "UTF-8");
    }

    @SneakyThrows
    public void run() {
        throw new Throwable();
    }
}

    編譯後:

public class SneakyThrows implements Runnable {
    @SneakyThrows(UnsupportedEncodingException.class)
    public String utf8ToString(byte[] bytes) {
        try {
            return new String(bytes, "UTF-8");
        } catch(UnsupportedEncodingException uee) {
            throw Lombok.sneakyThrow(uee);
        }
    }

    @SneakyThrows
    public void run() {
        try {
            throw new Throwable();
        } catch(Throwable t) {
            throw Lombok.sneakyThrow(t);
        }
    }
}

    9、@Synchronized

    ①、詳細介紹:這個註解用在類方法或者實例方法上,效果和synchronized關鍵字相同,區別在於鎖對象不同,對於類方法和實例方法,synchronized關鍵字的鎖對象分別是類的class對象和this對象,而@Synchronized的鎖對象分別是私有靜態final對象LOCK和私有final對象lock,當然,也可以自己指定鎖對象。

    ②、栗子:

    編譯前:

public class Synchronized {
    private final Object readLock = new Object();

    @Synchronized
    public static void hello() {
        System.out.println("world");
    }

    @Synchronized
    public int answerToLife() {
        return 42;
    }

    @Synchronized("readLock")
    public void foo() {
        System.out.println("bar");
    }
}

    編譯後:

public class Synchronized {
    private static final Object $LOCK = new Object[0];
    private final Object $lock = new Object[0];
    private final Object readLock = new Object();

    public static void hello() {
        synchronized($LOCK) {
            System.out.println("world");
        }
    }

    public int answerToLife() {
        synchronized($lock) {
            return 42;
        }
    }

    public void foo() {
        synchronized(readLock) {
            System.out.println("bar");
        }
    }
}

    10、@Log

    ①、詳細介紹:這個註解用在類上,可以省去從日誌工廠生成日誌對象這一步,直接進行日誌記錄,具體註解根據日誌工具的不同而不同,同時,可以在註解中使用topic來指定生成log對象時的類名。

    ②日誌總結,註解對應的日誌代碼:

@CommonsLog
==> private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);

@JBossLog
==> private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class);

@Log
==> private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());

@Log4j
==> private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);

@Log4j2
==> private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);

@Slf4j
==> private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);

@XSlf4j
==> private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);

    11、@Accessors

    ①、詳細介紹:通過該註解可以控制getter和setter方法的形式。

    ②、栗子:

      a)fluent 若為true,則getter和setter方法的方法名都是屬性名,且setter方法返回當前對象。

@Data
@Accessors(fluent = true)
class User {
	private Integer id;
	private String name;
	
	// 生成的getter和setter方法如下,方法體略
	public Integer id(){}
	public User id(Integer id){}
	public String name(){}
	public User name(String name){}
}

      b)chain 若為true,則setter方法返回當前對象。

@Data
@Accessors(chain = true)
class User {
	private Integer id;
	private String name;
	
	// 生成的setter方法如下,方法體略
	public User setId(Integer id){}
	public User setName(String name){}
}

      c)prefix 若為true,則getter和setter方法會忽視屬性名的指定前綴(遵守駝峰命名)

@Data
@Accessors(prefix = "f")
class User {
	private Integer fId;
	private String fName;
	
	// 生成的getter和setter方法如下,方法體略
	public Integer id(){}
	public void id(Integer id){}
	public String name(){}
	public void name(String name){}
}

 

四、Lombok的實際應用:

  a)添加Lombok的maven依賴:

技術分享圖片

  maven依賴:

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.2</version>
    <scope>provided</scope>
</dependency>

  b)只需要引入依賴後,就可以直接在開發時使用啦

  使用Lombok開發:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private String id;
    private String username;
    private String password;
    private Integer age;
    private String description;
}

  不使用Lombok開發:

package com.shuwen.demo.entity;


public class User {
    private String id;
    private String username;
    private String password;
    private Integer age;
    private String description;
    public User(){}
    public User(String id, String username, String password, Integer age, String description) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.age = age;
        this.description = description;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=‘" + id + ‘\‘‘ +
                ", username=‘" + username + ‘\‘‘ +
                ", password=‘" + password + ‘\‘‘ +
                ", age=" + age +
                ", description=‘" + description + ‘\‘‘ +
                ‘}‘;
    }
}

  經過這一對比,是不是很心動了呢,但是Lombok自身也有一定的缺陷,也需要註意哦~

五、Lombok的缺陷/易導致的錯誤

  通過官方文檔,可以得知,當使用@Data註解時,則有了@EqualsAndHashCode註解,那麽就會在此類中存在equals(Object other) 和 hashCode()方法,且不會使用父類的屬性,這就導致了可能的問題。

  問題:比如,有多個類有相同的部分屬性,把它們定義到父類中,恰好id(數據庫主鍵)也在父類中,那麽就會存在部分對象在比較時,它們並不相等,卻因為lombok自動生成的equals(Object other) 和 hashCode()方法判定為相等,從而導致出錯。

六、Lombok的缺陷解決方案

  解決方案:

  (1)使用@Getter @Setter @ToString代替@Data並且自定義equals(Object other) 和 hashCode()方法,比如有些類只需要判斷主鍵id是否相等即足矣。

  (2)或者使用在使用@Data時同時加上@EqualsAndHashCode(callSuper=true)註解

  時光匆匆飛逝,望你我皆有所收獲。

SpringBoot集成Lombok,讓代碼優雅起來