Tags:

Sinh code tự động với project lombok

Lombok là một thư viện java sinh code tự động theo một bản mẫu nhất định và ít khi thay đổi. Ví dụ trong 1 class sẽ có các biến private và chúng phải được triển khai hàm getter(), setter() hay constructor mặc định(không có tham số), constructor chứa tất cả các tham số đầu vào etc. Việc này lặp lại ở hầu hết các class làm cho mã java trở nên dài dòng và không có ý nghĩa trong thực tế, đây chính là lúc Lombok phát huy sức mạnh. 

Cách Lombok hoạt động

Lombok hoạt động như một bộ xử lý annotation nó tham gia vào quá trình biên dịch và tự động sinh mã java bytecode vào .class file tương ứng với các Lombok annotation được sử dụng.

Lombok chỉ giúp mã java đơn giản và gọn nhẹ và dễ bảo trì hơn, tuy nhiên khối lượng mã java được biên dịch thành bytecode khi dùng Lombok và khi không dùng là hoàn toàn bằng nhau.

Để sử dụng Lombok trong project Maven chúng ta chỉ cần thêm dependency của Lombok

<dependencies>
    ...
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.10</version>
        <scope>provided</scope>
    </dependency>
    ...
</dependencies>

Hoặc có thể xem cách cài đặt Lombok trên IntellIJ và Eclipse trong bài viết này.

Getter/Setter, constructor

Đóng gói các thuộc tính của đối tượng thông qua getter(), setter() method là 1 chuẩn chung trong lập trình hướng đối tượng. Hiện nay các IDE thông dụng cũng đã hỗ trợ sinh code tự động, tuy nhiên chúng vẫn được chứa trong .java file và không thể thay đổi khi các thuộc tính thay đổi(thay đổi tên, kiểu dữ liệu etc).

Xem một ví dụ về User class có 3 thuộc tính firstName, LastName, age

import lombok.Getter;
import lombok.Setter;
public class User {
    private String firstName;
    private String lastName;
    private int age;

    public User() {
    }

    public User(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public int getAge() {
        return age;
    }

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

Đây là 1 class đơn giản nhưng sau khi thêm constructorgetter(), setter() thì code đã trở nên rất nhiều và khó đọc. Hơn nữa chúng cũng không có ý nghĩa thực tế vì từ đầu đến cuối chúng ta phục vụ cho việc gán và lấy giá trị các thuộc tính.

Bây giờ hãy thử sử dụng Lombok cho User class.

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private String firstName;
    private String lastName;
    private int age;
}

Bằng cách thêm các annotation @Getter, @Setter Lombok sẽ tự động thêm getter(), setter() method cho tất cả các thuộc tính trong User class. @NoArgsConstructor sinh constructor mặc định trong khi @AllArgsConstructor sẽ sinh ra constructor với đầy đủ các thuộc tính firstName, lastName, age.

Lazy Getter

Đôi lúc, ứng dụng cần thực hiện một số biện pháp nhầm giảm thời gian chờ tải dữ liệu từ file, hoặc databse etc. Hoặc có thể tải đúng 1 lần khi chúng không thay đổi theo thời gian. Nó được gọi là Lazy Getter, giả sử các dữ liệu này được đặt trong 1 thuộc tính của class, cách để thực hiện Lazy Getter là chỉ lấy dữ liệu khi thuộc tính có giá trị null.

Lombok hỗ trợ Lazy Getter với @Getter (lazy = true)

public class GetterLazy {
 
    @Getter(lazy = true)
    private final Map<String, String> translates = readDatasFromFile();
 
    private Map<String, String> readDatasFromFile() {
 
        final Map<String, String> cache = new HashMap<>();
        List<String> rows = readDataFromFile();
 
        rows.forEach(s -> {
            String[] strs = s.split(",");
            cache.put(strs[0], strs [1]);
        });
 
        return cache;
    }
    
    private List<String> readDataFromFile() {
        // read file code
    }
}

Thuộc tính translates lưu trữ các bản phiên dịch từ tiếng anh sang tiếng việt được biên soạn sẵn, vì vậy chúng ta chỉ cần đọc chúng 1 lần và lưu vào translates sử dụng cho những lần sau.

Khi GetterLazy class được biên dịch

public Map<String, String> getTranslates() {
        Object value = this.translates.get();
        if (value == null) {
            synchronized(this.translates) {
                value = this.translates.get();
                if (value == null) {
                    Map<String, String> actualValue = this.readDatasFromFile();
                    value = actualValue == null ? this.translates : actualValue;
                    this.translates.set(value);
                }
            }
        }

        return (Map)((Map)(value == this.translates ? null : value));
    }

Chúng ta có thể thấy getTranslates() sẽ kiểm tra nếu translates null mới thực hiện lấy data và trả về kết quả.  synchronized để đồng bộ hóa translates tránh tình trạng nhiều thread đọc dữ liệu cho translates cùng lúc.

@RequiredArgsConstructor, @Accessors, @NoNull

@RequiredArgsConstructor annotation tạo ra một constructor cho các thuộc tính được yêu cầu xử lý đặc biệt như là các thuộc tính final chưa được khởi tạo thời điểm khai báo, các thuộc tính được đánh dấu là @NonNull etc. @NonNull đánh dấu các thuộc tính yêu cầu @RequiredArgsConstructor constructor phải kiểm tra và ném ngoại lệ khi giá trị khởi tạo cho chúng là null.

Mặc định Lombok theo chuẩn đặt tiền tố get, set trước getter(), setter() method. Ví dụ getter, setter của thuộc tính pepper là getPepper(), setPepper(). Nếu thấy nhàm chán với phong cách này, chúng ta có thể sử dụng @Accessors để tùy biến getter, setter method với 3 option:

  • fluent – Boolean value, nếu true Lombok sẽ bỏ qua tiền tố get, set của getter, setter method. Ví dụ pepper thì getter là pepper() và setter là pepper(T value).
  • chain – Boolean value, nếu true setter method sẽ trả về this thay vì void.
  • prefix – mảng string, các thuộc tính bắt buộc phải match với prefix của một trong số prefix trong mảng, nếu không sẽ không được khởi tạo getter, setter method. Lưu nó khi khớp với một prefix thì ký tự tiếp theo phải là ký tự in hoa, ví dụ prefix = f thì fName thõa, nhưng fname thì không.
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;

@Getter
@RequiredArgsConstructor
@Accessors(fluent = true)
public class User {
    private @NonNull String firstName;
    private @NonNull String lastName;
    private int age;
}

class test {
    public static void main(String[] agrs) {
        User user = new User("first name", "last name");
        System.out.println(user.firstName() + " - " + user.lastName() + " - "+ user.age());
    }
}

Output: first name – last name – 0

Note: int là kiểu dữ liệu nguyên thủy nên giá trị mặc định là 0.

@ToString và @EqualsAndHashCode 

@ToString anntation sẽ sinh toString() với tất cả các thuộc tính trong class, chúng ta không cần phải sữa đổi toString() method khi thay đổi các thuộc tính trong class. Cho các bạn chưa biết thì toString() thường được sử dụng để xuất các thông tin của các thuộc tính trong class cho mục đích log, đọc thông tin etc.

@EqualsAndHashCode sẽ sinh equals() hashCode() method, mặc định sẽ sử dụng tất cả các thuộc tính trong các hoạt động tính toán của chúng.

Builder Pattern Lombok

Builder pattern nằm trong nhóm Creational pattern giúp xây dựng các đối tượng phức tạp bằng các đối tượng đơn giản từng bước độc lập.

Lombok hỗ trợ triển khai Builder pattern thông qua @Builder annotation. Giả sử tạo một ApiClientConfiguration class lưu trữ các thông tin config của API. ApiClientConfiguration object nên được tạo một lần và không thể thay đổi sau khi đã khởi tạo.

@Builder
public class ApiClientConfiguration {
 
    private String host;
    private int port;
    private boolean useHttps;
 
    private long connectTimeout;
    private long readTimeout;
 
    private String username;
    private String password;
}

Chúng ta có thể khởi tạo ApiClientConfiguration object

ApiClientConfiguration config = 
    ApiClientConfiguration.builder()
        .host("https://shareprogramming.net//")
        .port(443)
        .useHttps(true)
        .connectTimeout(15000L)
        .readTimeout(5000L)
        .username("myusername")
        .password("secret")
    .build();

Release Resource

Java 7 giới thiệu try-with-resource để đảm bảo các tài nguyên được sử dụng bởi các instance của những class implement java.lang.AutoCloseable sẽ được giải phóng khi các thao tác hoàn tất. Lombok cung cấp một cách khác linh hoạt, ngắn gọn hơn với @Cleanup annotation.

@Cleanup anntation đảm bảo close() method sẽ được gọi khi các instance hoàn tất thao tác với các tài nguyên. Nếu các object cũng có nhu cầu giải phóng tài nguyên nhưng không phải là close() method mà chứa 1 method khác tương tự thì chúng ta có thể chỉ định tên như sau

@Cleanup("dispose") org.eclipse.swt.widgets.CoolBar bar = new CoolBar(parent, 0);

Ví dụ về @Cleanup với InputStream và OutputStream

import lombok.Cleanup;
import java.io.*;

public class CleanupExample {
  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[10000];
    while (true) {
      int r = in.read(b);
      if (r == -1) break;
      out.write(b, 0, r);
    }
  }
}

Code CleanupExample class sau khi biên dịch 

import java.io.*;

public class CleanupExample {
  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();
      }
    }
  }
}

Note: @Cleanup annotation không xử lý exception, nên chúng ta có thể throw lên method trên xử lý hoặc có thể try-catch xử lý ngay tại method. Cá nhân mình đánh giá là annotation này khá phế, nên sử dụng try-with-resource thì hơn.

@Synchronized

Để tránh việc nhiều thread sử dụng một phương thức tại một thời điểm, chúng ta thường khai báo phương thức sử dụng keyword synchronized của Java. Project Lombok cũng cung cấp một cách an toàn hơn để làm điều này bằng cách sử dụng annotation @Synchronized hoạt động trên static và instance method. Với annotation này, Lombok sẽ generate một private field tên là $lock cho phương thức nonstatic và một private field tên là $LOCK cho phương thức static. Điều này sẽ an toàn hơn bởi vì nó sẽ cho phép chúng ta lock phương thức sử dụng một instance field thay vì chính phương thức đó.

import lombok.Synchronized;

public class SynchronizedExample {
  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");
  }
}

Code sau khi biên dịch

public class SynchronizedExample {
  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");
    }
  }
}

De-Lombok

Nếu project của bạn đang sử dụng Lombok nhưng sau này bạn quyết định không dùng Lombok nữa! Lúc này project của bạn đã khá lớn và các Lombok annotation được sử dụng rất nhiều. Trong trường hợp này chúng ta sẽ phải làm gì đây?

Đừng lo lắm, Project đã xây dựng sẵn Delombok tool, cho phép generate mã code java từ bytecode được xây dựng bởi Lombok với các tính năng được giữ nguyên như khi dùng Lombok.

Tóm lược

Lombok là một thư viện mà mình luôn thêm vào khi bắt đầu một dự án mới vì sự đơn giản nhưng hiệu quả của nó mang lại. Khi dùng Lombok thì repository trên git của mình cũng nhẹ hẳn vì giảm được rất nhiều code.

Nguồn tham khảo

https://projectlombok.org/features/experimental/Accessors

https://projectlombok.org/features/constructor

https://www.baeldung.com/intro-to-project-lombok

https://projectlombok.org/features/Synchronized

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x