Sau bài giới thiệu tổng quan hôm qua thì hôm nay mình sẽ bắt đầu bài tiên về mastering spring boot. Trong bài này Dũng sẽ cùng các bạn đi về thời nguyên thuỷ để bắt đầu một chương trình Hello World (xin chào thế giới) với ApplicationContext, một đối tượng singleton để quản lý tất cả các singleton khác (các đối tượng khởi tạo duy nhất một lần) hoặc prototype (các đối tượng được khởi tạo mỗi lần sử dụng theo một mẫu nhất định). Nếu bạn muốn tìm hiểu kỹ hơn về singleton và prototype bạn có thể mua cuốn Làm chủ các mẫu thiết kế lập trình để đọc, và đừng quên nhập mã TMS để được giảm giá nhé. Còn bây giờ chúng ta sẽ đi vào chi tiết

Cài đặt môi trường

Phần này tương đối nhàm chán nhưng chúng ta vẫn phải làm thì mới có thể lập trình được, chúng ta sẽ cần cài các chương trình sau đây:

  1. JDK 17: Bạn có thể tải xuống tại trang tải xuống của oracle. Chỗ này cũng có một chút thú vị đó là, JDK (Java development kit) không chỉ được đầu tư, phát triển và duy trì bởi oracle mà nó còn được phát triển và duy trì bởi cộng đồng dưới cái tên OpenJDK, cho nên cũng sẽ có những sự khác biệt trong phần cài đặt của Oracle JDK và OpenJDK. Nguyên nhân của sự khác biệt đến từ việc Oracle bắt đầu tính phí sử dụng JDK của họ đối với doanh nghiệp vào năm 2018 thì phải, mình không nhớ chính xác. Vậy nên cộng đồng, trong đó có chính mình buộc phải sử dụng OpenJDK trên các máy chủ và đóng góp nhiều hơn cho OpenJDK thay vì phụ thuộc hoàn toàn vào Oracle JDK. Bạn có thể tải xuống OpenJDK tại đây. Tại sao lại phải cài JDK 17? Có vẻ như phiên bản mới nhất của Spring đang sử dụng phiên bản này.
  2. Cài đặt biến môi trường JAVA_HOME, đây làm một biến dạng tiêu chuẩn cho các chương trình sử dụng Java vậy nên chúng ta sẽ cần khai báo biến này. Bạn có thể tham khảo cách cài đặt tại đây.
  3. IntelliJ phiên bản cộng đồng: Bạn có thể tải xuống nó tại trang tải xuống của InteliJ, lưu ý rằng nó nằm ở giữa trang, bạn phải kéo xuống 1 chút mới thấy phần tải xuống bản community. Hồi đầu mới lập trình java thì mình hay dùng Eclipse, tuy nhiên về sau này InteliJ mạnh lên thì mình chuyển qua dùng vì thấy có nhiều điểm tiện hơn Eclipse đặc biệt là phần tìm kiếm.
  4. Maven: Đây là một công cụ hỗ trợ tổ chức mã nguồn và build java, bạn có thể cài đặt Maven theo hướng dẫn tại đây. Cũng có một công cụ khác tương tự như Maven đó là Gradle. Tuy nhiên công cụ này khá phức tạp cho người mới bắt đầu, nên mình khuyến khích bạn sử dụng Maven trước.

Khởi tạo dự án

Sau khi đã cài đặt xong các công cụ cần thiết, giờ là lúc chúng ta sẽ bắt đầu khởi tạo dự án. Bạn hãy mở IntelliJ của mình lên sau đó chọn: File > New > Project … Một giao diện popup sẽ hiển thị lên và bạn hãy nhập các thông tin dự án như sau:

Sau đó hãy nhấn Create, một giao diện dự án mastering-spring-boot sẽ hiển thị ra với cấu trúc thư mục như sau:

Ý nghĩa của các thư mục này sẽ là:

  1. Mastering-spring-boot: Là thư mục cha của dự án, chút nữa chúng ta sẽ khởi tạo các module con vào trong thư mục này.
  2. .idea: Là thư mục chứa cấu hình dự án của IntelliJ, chúng ta có thể bỏ qua nó.
  3. src: Là thư mục chứa tất cả các mã nguồn của dự án.
  4. main/java: Là thư mục chứa tất cả các mã nguồn java chính của dự án, khi chúng ta đóng gói lại thành tập tin .jar thì các mã nguồn trong thư mục này sẽ được năm trong tập tin .jar đó.
  5. main/resources: Là thư mục chứa các tập tin không phải java và cũng sẽ được đóng gói vào tập tin .jar. Về bản chất thì nó cũng tương tự như là thư mục main/java, có điều Maven muốn chúng ta tách bạch giữa mã nguồn Java và các loại mã nguồn khác để đảm bảo nguyên tắc single responsibility principle (mỗi thứ nên làm một chức năng riêng biệt) trong lập trình.
  6. test/java: Là thư mục chứa tất cả các mã nguồn java kiểm thử và khi build nó sẽ không được đưa vào tập tin jar.
  7. test/resources: Chúng ta cũng có thể tạo ra thư mục này để dành cho mục đích kiểm thử.
  8. .gitignore: Là tập tin chứa các thư mục hoặc tập tin sẽ không được commit vào git.
  9. pom.xml: Là tập tin cấu hình dự án maven, không có tập tin này dự án sẽ không thể hoạt động được với maven.
  10. External Libraries: Là một thư mục ảo do intelliJ sinh ra để gom lại các thư viện được sử dụng trong dự án.
  11. Scratches and Consoles: Cũng là một thư mục ảo do intelliJ sinh ra để gom lại cái tập tin thực thi hoặc các tập tin được sử dụng trong dự án do các plugin của intelliJ sinh ra, tạm thời bạn có thể bỏ qua thư mục này.

Cấu hình dự án

Khởi tạo dự án mới chỉ giúp chúng có được các thư mục cơ sở, bây giờ chúng ta sẽ cần cấu hình dự án để có thể thực sự bắt đầu lập trình được.

Cài đặt tập tin pom.xml cha

Mặc định thì dự án mastering-spring-boot mà chúng ta khởi tạo sẽ chỉ có một module là chính nó, cụ thể khi bạn nhấn đúp vào install trong panel bên phía tay phải thì dự án sẽ được build, và bạn sẽ thấy một tập tin mastering-spring-boot-1.0-SNAPSHOT.jar được tạo ra ở thự mục target, đây là kết quả của việc build và chúng ta có thể sử dụng tập tin .jar này để triển khai.

Tuy nhiên, thông thường một dự án của chúng ta sẽ có nhiều module, và trong loạt bài này mình sẽ tổ chức mỗi bài là một module, vậy nên chúng ta sẽ cần thay đổi tập tin pom.xml thành như sau:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>vn.techmaster</groupId>
    <artifactId>mastering-spring-boot</artifactId>
    <version>1.0.0</version>
    <packaging>pom</packaging>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>${maven.compiler.source}</source>
                    <target>${maven.compiler.target}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Chúng ta đã bổ sung thêm thẻ pom để nói rằng mastering-spring-boot là một thư mục cha sẽ chứa nhiều các module con và một plugin có tên maven-compiler-plugin, plugin này cũng không có gì đặc biệt, nó được sử dụng để thông báo cho trình biên dịch biết về phiên bản JDK được sử dụng. Ngoài ra chúng ta cũng đã đổi tên groupId từ org.example sang vn.techmaster để đúng với tên miền tổ chức của mình.
Bây giờ thư mục src đã không còn trở nên cần thiết, chúng ta có thể xoá nó đi, cấu trúc thư mục sẽ chỉ còn lại như sau:

Tạo module hello-world

Như đã nói thì mỗi bài viết chúng ta sẽ tạo một module, và module chúng ta sẽ tạo cho bài học này là hello-world. Bạn hãy trở lại intelliJ của mình và chọn File > New > Module và điền các thông tin như sau:

Sau đó nhấn Create, chúng ta sẽ thấy cấu trúc thư mục của dự án thay đổi như sau:

Đã có thêm một thư mục mới hello-world với cấu trúc giống y chang như thư mục master-spring-boot trước khi thay đổi, đây cũng là điểm hay của Maven khi nó tạo cho chúng ta cảm giác kế thừa và đỡ phải học thêm cái mới.
Trong tập tin mastering-spring-boot/pom.xml bạn cũng sẽ thấy một thẻ mới được sinh ra:

<modules>
    <module>hello-world</module>
</modules>

Cấu hình module hello-world

Mở tập tin hello-world/pom.xml chúng ta sẽ có:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.example</groupId>
        <artifactId>mastering-spring-boot</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>hello-world</artifactId>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

</project>

Trên thực tế trong tập tin mastering-spring-boot/pom.xml đã có:

<properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

Vậy nên theo don’t repeat yourself (đừng lặp lại) chúng ta sẽ có thể bỏ đoạn mã trung lặp và chúng ta sẽ có hello-world/pom.xml thay đổi như sau:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>vn.techmaster</groupId>
        <artifactId>mastering-spring-boot</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>hello-world</artifactId>
</project>

Vậy là việc cấu hình dự án đã xong, bây giờ chúng ta sẽ có thể chuyển đến các phần tiếp theo.

Phát biểu bài toán

Trong bài viết này chúng ta sẽ cùng nhau xây dựng ra một chương trình hiển thị dòng chữ Hello World ra màn hình, chỉ đơn giản vậy thôi.

Thiết kế mã nguồn

Ngoài “Học là có việc” thì mình và Techmaster cũng mong muốn các bạn sẽ trở thành chuyên gia trong lĩnh vực công nghệ thông tin, và để trở thành chuyên gia thì thiết kế là một yếu tố không thể thiếu được. Mặc dù chương trình của chúng ta đơn giản nhưng cũng nên thiết kế để phân chia nhiệm vụ cho các gói và các lớp, đây cũng là một trong những nguyên tắc sống còn trong lập trình. Cụ thể chúng ta sẽ có 3 gói như sau:

  1. Gói vn.techmaster.hello_world.service: Sẽ chứa lớp xử lý nghiệp vụ.
  2. Gói vn.techmaster.hello_world.controller: Sẽ chứa lớp tiếp nhận yêu cầu.
  3. Gói vn.techmaster.hello_world: Sẽ là lớp gọi, phát sinh yêu cầu và nhận về kết quả.
    Trong 3 gói chúng ta cũng sẽ có 3 lớp như sau:

  1. Lớp HelloWorldService: Thực thi nghiệp vụ hiển thị dòng chữ Hello World.
  2. Lớp HelloWorldController: Tiếp nhận yêu cầu và gọi đến đối tượng của lớp HelloWorldService.
  3. Lớp HelloWorldStartUp: Phát sinh yêu cầu và gọi đến đối tượng của lớp HelloWorldController.

Bắt đầu lập trình

Chúng ta sẽ cài đặt mã nguồn theo hai cách, không, và có sử dụng Spring để thấy được sự khác biệt và đồng thời cũng hiểu rõ được bản chất của vấn đề hơn.

Không dùng spring

Sau khi đã thiết kế xong, giờ là lúc chúng ta sẽ xoá các lớp mặc định do intelliJ sinh ra, sau đó tạo ra các gói và các lớp như sau:

Chúng ta sẽ cài đặt các lớp theo mã nguồn như sau:
Lớp HelloWorldService:

package vn.techmaster.hello_world.service;

public class HelloWorldService {

    public void helloWorld() {
        System.out.println("Hello World");
    }
}

Lớp HelloWorldController:

package vn.techmaster.hello_world.controller;

import vn.techmaster.hello_world.service.HelloWorldService;

public class HelloWorldController {

    private final HelloWorldService helloWorldService;

    public HelloWorldController(HelloWorldService helloWorldService) {
        this.helloWorldService = helloWorldService;
    }

    public void handleHelloWorldRequest() {
        helloWorldService.helloWorld();
    }
}

Lớp HelloWorldStartUp:

package vn.techmaster.hello_world;

import vn.techmaster.hello_world.controller.HelloWorldController;
import vn.techmaster.hello_world.service.HelloWorldService;

public class HelloWorldStartUp {

    public static void main(String[] args) {
        HelloWorldService helloWorldService = new HelloWorldService();
        HelloWorldController helloWorldController = new HelloWorldController(
            helloWorldService
        );
        helloWorldController.handleHelloWorldRequest();
    }
}

Bây giờ hãy chạy lớp HelloWorldStartUp bằng cách nhấn vào nút play bên cạnh tên lớp HelloWorldStartUp

Kết quả chúng ta nhận được sẽ là:

Thật tuyện vời đúng không, chương trình chạy ngay trong lần đầu tiên, đúng là khi đã có thiết kế đầy đủ thì việc lập trình còn dễ hơn cả ăn kẹo.

Cài đặt dùng spring

Để sử dụng spring chúng ta sẽ cần bổ sung phụ thuộc vào tập tin hello-world/pom.xml như sau:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>vn.techmaster</groupId>
        <artifactId>mastering-spring-boot</artifactId>
        <version>1.0.0</version>
    </parent>

    <artifactId>hello-world</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.1.8</version>
        </dependency>
    </dependencies>
</project>

Chúng ta đã bổ sung thư viện spring-context vào danh sách phụ thuộc của dự án, và spring-context cũng chính là thư viện trái tim của Spring và chung ta sẽ tìm hiểu sâu về nó ở các bài viết tiếp theo. Bây giờ bạn hãy nhấn vào nút reload để intelliJ tải xuống các thư viện phụ thuộc.

Tiếp theo chúng ta sẽ bổ sung annotation @Component vào các lớp như sau:

package vn.techmaster.hello_world.service;

import org.springframework.stereotype.Component;

@Component
public class HelloWorldService {

    public void helloWorld() {
        System.out.println("Hello World");
    }
}

package vn.techmaster.hello_world.controller;

import org.springframework.stereotype.Component;
import vn.techmaster.hello_world.service.HelloWorldService;

@Component
public class HelloWorldController {

    private final HelloWorldService helloWorldService;

    public HelloWorldController(HelloWorldService helloWorldService) {
        this.helloWorldService = helloWorldService;
    }

    public void handleHelloWorldRequest() {
        helloWorldService.helloWorld();
    }
}

Chi tiết về annotation @Component chúng ta cũng sẽ tìm hiểu ở các bài viết tiếp theo. Bây giờ đến phần quan trọng chúng ta sẽ cần thay đổi lớp HelloWorldStartUp thành như sau:

package vn.techmaster.hello_world;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import vn.techmaster.hello_world.controller.HelloWorldController;

public class HelloWorldStartUp {

    public static void main(String[] args) {
        ApplicationContext applicationContext =
            new AnnotationConfigApplicationContext(
                "vn.techmaster.hello_world"
            );
        HelloWorldController helloWorldController = applicationContext
            .getBean(HelloWorldController.class);
        helloWorldController.handleHelloWorldRequest();
    }
}

Bạn có thấy rằng việc khởi tạo các lớp bằng từ khoá new đã biến mất? Đó là nhờ vào khả năng khởi tạo và quản lý các đối tượng mà chúng ta sẽ tìm hiểu chi tiết sau, ở đây chúng ta có thể hiểu đơn giản rằng:

  1. Spring cung cấp cho chúng ta một lớp AnnotationConfigApplicationContext.
  2. Lớp này sẽ đi quét qua tất cả các gói được đề cập đến, trong trường hợp này là “vn.techmaster.hello_world”.
  3. Lớp này sẽ đi khởi tạo tất cả các đối tượng có liên quan được đánh dấu với annotation @Component.
  4. Khi chính ta cần lấy bất kỳ đối tượng nào thì chúng ta sẽ gọi applicationContext.getBean để lấy.
    Khi chạy chương trình chúng ta vẫn sẽ nhận được kết quả tương tự như không sử dụng Spring.

Những khái niệm quan trọng

Ở đây có một số khái niệm quan trọng và thú vị mà mình nghĩ các bạn sẽ có câu hỏi trong đầu.

Tại sao tên lớp lại là ApplicationContext?

Chúng ta sẽ tách thành hai từ Application và Context để hiểu rõ hơn.

  1. Application dịch ra tiếng Việt là ứng dụng. Khi chúng ta đang chạy với intelliJ thì chúng ta dùng khái niệm chương trình để nói rằng một đoạn mã đang thực thi một công việc nào đó, tuy nhiên trong tương lai khi chúng ta đóng gọi mọi thứ vào tập tin .jar và triển khai ở một máy chủ nào đó thì lúc này chúng ta sẽ nó rằng mình đang chạy một ứng dụng, nó cũng tương tự như ứng dụng World, Excel hay intelliJ chỉ khác là nó có thể không có giao diện người dùng mà thôi.
  2. Context dịch ra tiếng việt là bối cảnh. Nếu bạn đã từng lập trình với Android hay EzyFox Server rồi thì bạn sẽ không còn xa lạ gì với khái niệm này nữa. Nó được dùng để nói về một lớp bao quát, quản lý các thành phần (các đối tượng, cấu hình) trong một chương trình hay một ứng dụng trong một phạm vi nhất định. Có thể là phạm vi cả chương trình, phạm vi một phiên làm việc …
    Nói tóm lại việc đặt tên ApplicationContext sẽ giúp lập trình viên hiểu được rằng đây là một lớp quản lý các thành phần của toàn bộ một ứng dụng.

Bean là gì?

Đây là một khái niệm Spring sinh ra để bao gồm hai khái niệm khác là singleton và prototype mà chúng ta sẽ cùng nhau tìm hiểu trong bài tiếp theo.

Tổng kết

Chúng ta đã cùng nhau:

  1. Tìm hiểu về cách khởi tạo một dự án nhiều module với intelliJ và maven.
  2. Thiết kế và cặt đặt một chương trình Hello World đơn giản không dùng và dùng spring.
  3. Tìm hiểu một số khái niệm mà Spring đưa ra.
    Việc nắm chắc các kiến thức nền tảng trong bài viết này sẽ giúp ích chúng ta rất nhiều trong các bài viết tiếp theo, vậy nên các bạn có thể đọc kỹ bài này nhé.

Sách tham khảo

Những nguyên tắc sống còn trong lập trình Mã giảm giá Tech10 các bạn nhé.


Cám ơn bạn đã quan tâm đến bài viết Hello World này. Để nhận được thêm các kiến thức bổ ích bạn có thể:

  1. Đọc các bài viết của TechMaster trên facebook: https://www.facebook.com/techmastervn
  2. Xem các video của TechMaster qua Youtube: https://www.youtube.com/@TechMasterVietnam nếu bạn thấy video/bài viết hay bạn có thể theo dõi kênh của TechMaster để nhận được thông báo về các video mới nhất nhé.
  3. Chat với techmaster qua Discord: https://discord.gg/yQjRTFXb7a