Trong bài viết này sẽ giới thiệu đến với các bạn những điểm mới nổi bật cần lưu ý cho Developer trong gói Spring Boot 3, được Spring Team phát hành vào tháng 12 năm 2022. Một số điểm nổi bật như khả năng tương thích với JDK 17, build ứng dụng với GraalVM, etc.

Xin chào các bạn đã quay trở lại với AutoCode.VN,

1. Spring Boot 3 chính thức release:

Trong tháng 12 vừa qua cộng đồng Spring Framework đã đón nhận một tin vui từ công ty mẹ của Spring đã chính thức thông báo đã release Spring Boot version 3. Lập trình viên Andy Wilkinson đã đại diện thông báo rằng Spring Boot 3 đã có thể tìm thấy trên thư viện Maven repository. Đây là kết quả của 12 tháng làm việc với hơn 57000 lượt commits code của 151 lập trình viên. Trong tuyên bố ông cũng gửi lời cảm ơn chân thành đến với tất cả những người đã đóng góp và những lập trình viên đã sử dụng sớm và cho kết quả phản hồi liên tục để sản phẩm liên tục được cập nhật và sửa lỗi.

Andy Wilkinson - source: spring

Spring Boot 3 là một phiên bản release lớn mới nhất kể từ khi Spring Boot 2.0 cách đây 4 năm rưỡi. Đây cũng là bản Spring Boot đầu tiên được xây dựng để hỗ trợ Spring Framework 6.0, một số thư viện khác trong Spring Project cũng được update theo như sau:

 

2. Giới thiệu một số feature mới của Spring Boot 3 và ví dụ:

Chúng ta sẽ cùng điểm qua một số feature mới nổi bật thông qua code ví dụ.

2.1 Java 17 supports:

Spring Boot 3 được xây dựng để hỗ trợ Spring Framework 6 và JDK 17, các lập trình viên của Spring đã đưa ra khuyến nghị Spring Boot 3 nên được sử dụng tốt nhất với JDK 17, có vẻ như kể đây công ty mẹ của Spring đã có ý định dần thay thế và muốn người dùng nâng cấp cho bản JDK 8 đã được sử dụng trong một thời gian khá dài, trong khi những bản nâng cấp LTS tiếp theo như JDK 11 cũng không có quá nhiều khác biệt với JDK 8. Nhưng với JDK 17 câu chuyện lại hoàn toàn khác với những feature trước đây chỉ có thể sử dụng thông qua các phiên bản preview, ví dụ như Records trong JDK 14 Preview, thì nay đã được chính thức đưa vào trong JDK 17. Hãy cùng điểm qua một số feature nổi bật:

2.1.1. Records: 

Lợi ích của Records mang lại chính là tối giản được nhiều dòng code khi chúng ta cần định nghĩa các class DTO với mục đich trao đổi dữ liệu mà các field của class không thay đổi nhiều. Xét ví dụ sau, nếu chúng ta cần định nghĩa một class PersonDTO theo kiểu thông thường, sẽ viết như sau:

public class PersonDTO {
    private String name;
    private int age;
    private String department;

    public int getAge() {
        return age;
    }

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

    public String getDepartment() {
        return department;
    }

    public void setDepartment(String department) {
        this.department = department;
    }

    public String getName() {
        return name;
    }

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

    public int calculateSalary() {
        return 0;
    }
}

Với đoạn code trên có thể thấy chúng ta phải viết khá nhiều getter & setters lặp đi lặp lại.

Nhưng với Records, chúng ta có thể viết gọn lại như sau, cho cùng tính năng với đoạn code trên:

public record PersonDTO(String name, int age, String department) {
    public int calculateSalary() {
        return 0;
    }
}

Như các bạn đã thấy, chúng ta hoàn toàn lược bỏ đi việc khai báo các field private và các getter & setter tương ứng, và sử dụng class PersonDTO như sau:

public static void main(String[] args) {
        PersonDTO personDTO = new PersonDTO("Person A", 29, "IT");
        System.out.println(personDTO.name());
        System.out.println(personDTO.age());
        System.out.println(personDTO.calculateSalary());
    }

Cần lưu ý rằng khi sử dụng Records: 

- Các field của 1 class Record là private final

- Record chỉ được phép khai báo 1 constructor duy nhất là  constructor với các tham số là toàn bộ các field và theo thứ tự.

- Người dùng có thể tự khai báo thủ công constructor như trên, nhưng phải set giá trị init cho tất cả các field, như ví dụ sau:

public record PersonDTO(String name, int age, String department) {
    public PersonDTO(String name, int age, String department) {
        this.name = name;
        this.age = age;
        this.department = department;
    }

    public int calculateSalary() {
        return 0;
    }
}

 

2.1.2. Text Blocks:

Feature Text Blocks lần đầu được tiếp cận người dùng ở phiên bản JDK 14 Preview,  nhưng mãi tới JDK 17 Text Blocks mới được chính thức được đưa vào sử dụng. Text Blocks cung cấp cho lập trình viên các phương pháp định nghĩa một chuỗi String một cách thuận tiện hơn so với dùng kiểu nỗi chuỗi thông thường.

Để sử dụng Text Blocks chúng ta cần khai báo từ khoá là cặp dấu 3 nháy ("""), và là cặp dấu nháy 3 này phải nằm trên 2 line khác nhau, nếu để chúng trên cùng 1 line thì sẽ có lỗi compile, như ví dụ sau:

String example = """
     Example text""";

Các lợi ích của Text Blocks:

- Break line & Identation:  Giữ được định dạng xuống hàng và thụt lề như cách chúng ta viết trong IDE. Ví dụ như sau:

public static void main(String[] args) {
        String example =  """
            <html>

                <body>
                    <span>example text</span>
                </body>
            </html>""";
        System.out.println(example);
    }

Kết quả sau khi chạy:

<html>

    <body>
        <span>example text</span>
    </body>
</html>

 

Escaping Double-Quotes - viết dấu nháy đôi: 

Escaping Line Terminators - viết 1 chuỗi String liên tiếp trên nhiều dòng: Thông thường chúng ta có thể tạo 1 chuỗi String trên nhiều line bằng dấu + , như ví dụ sau:

 public static void main(String[] args) {
        String s = " line 1 " +
                "still line 1 " +
                "and line 1 at all ";
        System.out.println(s);
    }

Và hiển nhiên chúng ta có kết quả là chuỗi String chỉ có 1 line :

 line 1 still line 1 and line 1 at all 

Với Text Blocks chúng ta có cách viết thuận tiện hơn:

public static void main(String[] args) {
        String s = """ 
                line 1 \
                still line 1 \
                and line 1 at all """;
        System.out.println(s);
    }

Bằng cách sử dụng dấu \ trong chuỗi Text Blocks, chúng ta sẽ không cần phải quan tâm đến việc ngắt đoạn câu. Và hơn thế khi kết hợp với 2 đặc điểm trên với nhau, chúng ta có thể định nghĩa 1 chuỗi String vừa có dấu xuống hàng và vừa xuống hàng trong code cho dễ đọc, như ví dụ sau:

public static void main(String[] args) {
        String s = """ 
                this is the first part of very long line and it should keeps \
                going on multiple lines of line of code \
                but actually it's still an one-line String.
               And here's a new line """ ;
        System.out.println(s);
    }

Kết quả sau khi chạy:

 this is the first part of very long line and it should keeps  going on multiple lines of line of code  but actually it's still an one-line String.
And here's a new line

 

2.1.3. Switch expression: 

Với JDK 17 chúng ta được nhận một số cải tiến lớn cho switch expression từ phiên bản JDK 13, có thể kế đến như sau:

- Object đầu vào có thể có bất kỳ Data Type nào, không còn bị giới hạn bởi ENUM hay primitive type như trước đó

- Object đầu vào có thể có giá trị null.

Cùng xem ví dụ sau:

private static String doNewSwitch(Object obj) {
        return switch (obj) {
            case Integer i -> "It is an integer";
            case String s -> "It is a string";
            case Employee employee && employee.dept().equals("IT") -> "IT Employee";
            case null -> "Null data";
            default -> {
                var s = "Unknown ";
                yield s + "data type" ;
            }
        };

    }

Trong ví dụ trên, chúng ta có thể thấy rằng chúng ta có thể xử lý Object đầu vào dựa vào Data type của nó, đồng thời 

- Mỗi case của switch có thể return về 1 giá trị bằng từ khoá yiel hoặc dấu lamda. Như ví dụ trên, chúng ta thấy rằng giá trị của case default sẽ là kết quả của 1 block code từ 2 line of code trở lên, do đó chúng ta sử dụng từ khoá yield để return kết quả, có thể xem yield là từ khoá return trong case của switch expression.

 

2.2 Thư viện Jakarta:

Như đã đề cập ở trên Spring Boot 3 hỗ trợ cho Spring Framework 6 , JDK 17 và cũng đồng thời hỗ trợ cho nền tảng JEE mới là Jakarta EE 10. 

Về mặt sử dụng chúng ta sẽ thay đổi các class thuộc package "javax." thành package "jakarta.", do vậy để có thể sử dụng được Spring Boot 3 một cách tối ưu và toàn diện nhất thì chúng ta cần chỉnh sửa lại toàn bộ các package đang sử dụng "javax." thành package "jakarta.". Nếu chúng ta bắt đầu xây dựng một project mới hoàn toàn mới bằng Spring Boot 3 thì được khuyến nghị nên sử dụng hoàn toàn package "jakarta." thay vì "javax."

Ví dụ:

import javax.servlet.http.HttpServletRequest;

đổi thành:

import jakarta.servlet.http.HttpServletRequest;

Thêm một  Ví dụ khi định nghĩa 1 ORM Entity:

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private String fullName;

    // getters & setters
}

 

2.3 Spring Native:

Spring Native cung cấp hỗ trợ cho ứng dụng Spring có thể được build bằng GraalVM để tối ưu hoá cấu trúc của file thực thi đồng thời nâng cao performance cho ứng dụng Spring.

Vậy GraalVM là gì ?

GraalVM có website chính thức tại địa chỉ https://www.graalvm.org/ . Theo định nghĩa tại website, GraalVM là một bộ JDK có khả năng nâng cao performance cho những ứng dụng được viết bằng Java và những ngôn ngữ khác mà có thể vận hành trên nền tảng JVM (máy ảo Java). GraalVM trên thị trường hiện có 2 phiên bản là GraalVM Enterprise được phát triển trên nền tảng Oracle JDK, và 1 phiên bản GraalVM Community được phát triển trên nền tảng của OpenJDK. GraalVM hỗ trợ các nền tảng Linux và MacOS x86 64 bit, ARM 64-bit  và Windows x86 64-bit.

Như vậy, đến đây chúng ta có thể hình dung rằng GraalVM hoàn toàn tương tự như một bộ JDK chúng ta vẫn hay sử dụng, chỉ khác là GraalVM hỗ trợ tối ưu file thực thi và nâng cao performance khi vận hành ứng dụng.

Chúng ta phải cài đặt máy ảo GraalVM lên máy trước khi build ứng dụng Spring theo hướng Spring native, như đã đề cập ở trên GraalVM hỗ trợ hầu hết các nền tảng phổ biến hiện nay như Linux, MacOs và Windows. Cách cài đặt cũng tương tự chúng ta cài đặt JDK thông thường. Để cài đặt, chúng ta truy cập trang download GraalVM chính chủ tại địa chỉ https://www.graalvm.org/downloads/ 

Cài đặt GraalVM và build ứng dụng Spring Boot:

Bước 1 - Cài đặt GraalVM:

- Cài đặt GraalVM hoàn toàn tương tự như việc cài đặt JDK, sau khi download bản GraalVM tương ứng với nền tảng máy tính của bạn, ví dụ chúng ta chọn phiên bản MacOS JDK 17

- Giải nén file download, ví dụ chúng ta giải nén ra thư mục /Users/user1/software/graalvm-ce-java17-22.3.0.

- Thiết lập biến môi trường GRAALVM_HOME:

export GRAALVM_HOME="/Users/user1/software/graalvm-ce-java17-22.3.0/Contents/Home"
export JAVA_HOME=/Users/user1/software/graalvm-ce-java17-22.3.0/Contents/Home

Như các bạn có thể thấy rằng, chúng ta set JAVA_HOME vào folder của GraalVM, vì như đã giới thiệu ở trên GraalVM về bản chất cũng là 1 phiên bản JDK.

Kiểm tra kết quả cài đặt bằng cách chạy lệnh sau:

java -version

Và chúng ta có kết quả tương tự như sau:

openjdk version "17.0.5" 2022-10-18
OpenJDK Runtime Environment GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08)
OpenJDK 64-Bit Server VM GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08, mixed mode, sharing)

Lưu ý: kết quả output phải có thông tin của GraalVM, như ví dụ ở trên chúng ta đang sử dụng phiên bản GraalVM 22.3.0

 

Bước 2 - Build ứng dụng Spring Boot:

Sau khi hoàn tất cài đặt GraalVM, chúng ta tiến hành build ứng dụng, trong ví dụ này chúng ta sử dụng Maven làm ví dụ mình hoạ:

mvn -Pnative -DskipTests native:compile

Kết quả tương tự như sau:

====================================================================================================================
GraalVM Native Image: Generating 'appname' (executable)...
====================================================================================================================
Warning: Method com.zaxxer.hikari.HikariConfig.getScheduledExecutorService() not found.
Warning: Method com.zaxxer.hikari.HikariConfig.isInitializationFailFast() not found.
Warning: Method com.zaxxer.hikari.HikariConfig.isJdbc4ConnectionTest() not found.
Warning: Method com.zaxxer.hikari.HikariConfig.setInitializationFailFast(boolean) not found.
Warning: Method com.zaxxer.hikari.HikariConfig.setJdbc4ConnectionTest(boolean) not found.
Warning: Method com.zaxxer.hikari.HikariConfig.setScheduledExecutorService(ScheduledThreadPoolExecutor) not found.
Warning: Could not resolve org.jboss.logmanager.LogManager for reflection configuration. Reason: java.lang.ClassNotFoundException: org.jboss.logmanager.LogManager.
Warning: Could not resolve [Lorg.apache.logging.log4j.core.Appender; for reflection configuration. Reason: java.lang.ClassNotFoundException: org.apache.logging.log4j.core.Appender.
Warning: Could not resolve [Lorg.apache.logging.log4j.core.config.AppenderControl; for reflection configuration. Reason: java.lang.ClassNotFoundException: org.apache.logging.log4j.core.config.AppenderControl.
Warning: Could not resolve [Lorg.apache.logging.log4j.core.config.AppenderRef; for reflection configuration. Reason: java.lang.ClassNotFoundException: org.apache.logging.log4j.core.config.AppenderRef.
Warning: Could not resolve [Lorg.apache.logging.log4j.core.config.LoggerConfig; for reflection configuration. Reason: java.lang.ClassNotFoundException: org.apache.logging.log4j.core.config.LoggerConfig.


...

Lưu ý rằng GraalVM sẽ tốn nhiều thời gian hơn để build ứng dụng so với JDK thông thường, nhưng đổi lại hiệu quả performance sẽ vượt trrội hơn rất nhiều, từ thời gian khởi động app cho đến lượng tài nguyên sử dụng

 

Bước 3 - Chạy ứng dụng: 

Sau khi hoàn tất build ứng dụng, chúng ta sẽ tìm file dùng để chạy ứng dụng bằng kiểm tra file artifact trong thư mục target: target/<app_name>.build_artifacts.txt, nội dung file sẽ tương tự như sau:

[EXECUTABLE]
appname

Để chạy ứng dụng, chúng ta chạy lệnh:

./target/<app_name>

Một điều cần lưu ý rằng chúng ta không cần phải sử dụng lệnh java -jar như thông thường mà chỉ cần thực thi file <app_name> như một file executable. Output sẽ tương tự như sau:

2023-01-14T22:11:17.928+07:00  INFO 10423 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2023-01-14T22:11:17.928+07:00  INFO 10423 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2023-01-14T22:11:17.933+07:00  WARN 10423 --- [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2023-01-14T22:11:17.984+07:00  INFO 10423 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8870 (http) with context path ''
2023-01-14T22:11:17.984+07:00  INFO 10423 --- [           main] appname.Application          : Started Application in 0.151 seconds (process running for 0.161)

Và kết quả chúng ta có thể thấy rằng thời gian khởi động app đã được rút ngắn đáng kể, chỉ khoảng 0,1s cho một ứng dụng Spring, tất nhiên ứng dụng trong ví dụ này có kích thước khá nhỏ khoảng 500 line of code.

 

 

minhnhatict@gmail.com