
Yo Gen Z coder, chuẩn bị tinh thần cho một buổi 'đập hộp' kiến thức mà đảm bảo sẽ khiến project của mấy đứa 'lên level' một cách kinh ngạc! Hôm nay, anh Creyt sẽ 'phanh phui' cái bí mật mang tên Modules trong Java – hay còn gọi là Java Platform Module System (JPMS), đứa con cưng từ Java 9.
1. Modules là gì? 'Ngăn Kéo Thần Kỳ' Của Code!
Tưởng tượng thế này: project của mấy đứa ban đầu chỉ là một căn phòng nhỏ với vài món đồ lặt vặt (class). Dễ quản lý, đúng không? Nhưng rồi, căn phòng lớn dần, biến thành cả một căn nhà, rồi một khu chung cư, cuối cùng là một siêu đô thị khổng lồ với hàng ngàn căn hộ, cửa hàng, công viên... Lúc này, nếu không có quy hoạch, không có các 'quận', 'phường' rõ ràng, thì đúng là 'mớ bòng bong' luôn!
Modules chính là những 'quận', 'phường' trong cái siêu đô thị code của mấy đứa. Nó không chỉ là tập hợp các gói (packages) như cách mấy đứa vẫn làm, mà nó còn định nghĩa rành mạch:
- Mình có gì để 'khoe' ra ngoài? (Những package nào được phép truy cập từ module khác).
- Mình cần 'mượn' gì từ 'nhà hàng xóm'? (Những module nào mình phụ thuộc, cần dùng).
Nói cách khác, Modules giúp mấy đứa đóng gói code ở một cấp độ cao hơn package, tạo ra các đơn vị độc lập, tự chủ hơn. Mục đích cuối cùng? Code sạch hơn, dễ bảo trì hơn, dễ mở rộng hơn, và quan trọng nhất là 'dependency hell' (ác mộng phụ thuộc) sẽ không còn là nỗi ám ảnh nữa! Nó giống như mỗi 'quận' có cổng riêng, chỉ cho phép những ai có giấy phép mới được vào, và chỉ cho phép người dân trong quận ra ngoài qua những cổng nhất định vậy. 'Cực kỳ bảo mật và có tổ chức' đúng không?

2. Code Ví Dụ Minh Hoạ: Xây Dựng 'Ngân Hàng Mini'
Để mấy đứa dễ hình dung, mình cùng xây dựng một hệ thống ngân hàng mini với hai module:
com.mybank.core: Chứa logic nghiệp vụ cốt lõi (ví dụ: tài khoản ngân hàng).com.mybank.ui: Chứa giao diện người dùng, cần truy cập logic từcore.
Bước 1: Tạo Module com.mybank.core
Trong thư mục src/com.mybank.core, tạo file module-info.java:
// src/com.mybank.core/module-info.java
module com.mybank.core {
exports com.mybank.core.model; // Cho phép module khác truy cập gói này
}
Và lớp BankAccount trong gói com.mybank.core.model:
// src/com.mybank.core/com/mybank/core/model/BankAccount.java
package com.mybank.core.model;
public class BankAccount {
private String accountNumber;
private double balance;
public BankAccount(String accountNumber, double initialBalance) {
this.accountNumber = accountNumber;
this.balance = initialBalance;
}
public void deposit(double amount) {
if (amount > 0) {
this.balance += amount;
System.out.println("Deposited " + amount + " to account " + accountNumber);
}
}
public void withdraw(double amount) {
if (amount > 0 && this.balance >= amount) {
this.balance -= amount;
System.out.println("Withdrew " + amount + " from account " + accountNumber);
} else {
System.out.println("Insufficient funds or invalid amount for account " + accountNumber);
}
}
public double getBalance() {
return balance;
}
public String getAccountNumber() {
return accountNumber;
}
@Override
public String toString() {
return "Account " + accountNumber + ", Balance: " + balance;
}
}
Bước 2: Tạo Module com.mybank.ui
Trong thư mục src/com.mybank.ui, tạo file module-info.java:
// src/com.mybank.ui/module-info.java
module com.mybank.ui {
requires com.mybank.core; // Khai báo phụ thuộc vào module com.mybank.core
}
Và lớp BankApp (lớp chính để chạy ứng dụng):
// src/com.mybank.ui/com/mybank/ui/BankApp.java
package com.mybank.ui;
import com.mybank.core.model.BankAccount; // Import từ module com.mybank.core
public class BankApp {
public static void main(String[] args) {
System.out.println("Welcome to MyBank App!");
// Tạo một tài khoản mới từ module core
BankAccount account1 = new BankAccount("12345", 1000.0);
System.out.println(account1);
account1.deposit(200.0);
System.out.println(account1);
account1.withdraw(300.0);
System.out.println(account1);
account1.withdraw(1000.0); // Thử rút quá số dư
System.out.println(account1);
}
}
Bước 3: Biên Dịch và Chạy
Giả sử cấu trúc thư mục của bạn như sau:
.
├── src
│ ├── com.mybank.core
│ │ ├── com
│ │ │ └── mybank
│ │ │ └── core
│ │ │ └── model
│ │ │ └── BankAccount.java
│ │ └── module-info.java
│ └── com.mybank.ui
│ ├── com
│ │ └── mybank
│ │ └── ui
│ │ └── BankApp.java
│ └── module-info.java
└── out
Biên dịch:
# Tạo thư mục đầu ra cho các module đã biên dịch
mkdir -p out/com.mybank.core
mkdir -p out/com.mybank.ui
# Biên dịch module com.mybank.core
javac -d out/com.mybank.core --module-source-path src src/com.mybank.core/module-info.java src/com.mybank.core/com/mybank/core/model/BankAccount.java
# Biên dịch module com.mybank.ui, cần biết module core ở đâu
javac -d out/com.mybank.ui --module-source-path src --module-path out src/com.mybank.ui/module-info.java src/com.mybank.ui/com/mybank/ui/BankApp.java
Chạy ứng dụng:
java --module-path out -m com.mybank.ui/com.mybank.ui.BankApp
Kết quả sẽ hiển thị các thao tác gửi/rút tiền của tài khoản. Đây là minh chứng rõ ràng nhất cho việc module com.mybank.ui đã thành công 'mượn' được BankAccount từ com.mybank.core nhờ khai báo requires và exports.
3. Mẹo Hay (Best Practices) Từ 'Lão Làng' Creyt
- 'Ít là nhiều' khi Export: Chỉ
exportsnhững package nào thật sự cần thiết cho module khác sử dụng. Đừng có 'khoe' hết ra, đó là cách để bảo vệ 'nội thất' bên trong và tránh rò rỉ thông tin không cần thiết. Giống như bạn chỉ mở cửa chính ra đón khách, chứ không phải mở toang cả nhà kho! - Khai báo
requiresrõ ràng: Mỗi khi module của bạn cần dùng đến code của module khác, hãy khai báorequiresmột cách minh bạch trongmodule-info.java. Điều này giúp hệ thống biết được các phụ thuộc và tránh lỗi runtime. - Chia module hợp lý: Đừng chia quá vụn vặt (mỗi package một module) cũng đừng gộp quá lớn (cả project một module). Hãy chia theo các lĩnh vực nghiệp vụ hoặc tầng kiến trúc (ví dụ:
core,service,dao,ui,util). - Tên module có ý nghĩa: Đặt tên module theo chuẩn Reverse Domain Name (ví dụ:
com.mycompany.product.subsystem) để tránh xung đột và dễ nhận diện.
4. Ứng Dụng Thực Tế và 'Thử Nghiệm'
- JDK (Java Development Kit) tự thân: Ví dụ điển hình nhất là chính Java Runtime Environment (JRE). Từ Java 9, toàn bộ JDK đã được modular hóa. Khi bạn chạy một ứng dụng Java, JVM chỉ tải những module cần thiết (như
java.base,java.sql,java.desktop...) thay vì cả cục JRE khổng lồ như trước. Điều này giúp giảm kích thước runtime, tối ưu hiệu năng. - Các Framework lớn: Dù không phải tất cả các ứng dụng Spring Boot đều tận dụng JPMS cho cấu trúc ứng dụng của họ, nhưng bản thân Spring Framework và nhiều thư viện lớn khác đã được modular hóa, cho phép bạn chọn lọc các thành phần cần thiết.
- Microservices trong Monolith: Nghe có vẻ hơi ngược đời, nhưng bạn có thể dùng Modules để tạo ra các "đơn vị dịch vụ" độc lập ngay trong một ứng dụng monolith lớn. Mỗi module có thể coi như một "microservice ảo", giúp phân tách code rõ ràng, dễ dàng refactor ra microservice thật sau này.
Anh Creyt đã từng 'vật lộn' với JPMS khi nó mới ra mắt. Ban đầu có vẻ hơi rắc rối với các file module-info.java và các lệnh biên dịch/chạy phức tạp hơn. Nhưng sau khi 'thấm đòn' thì thấy nó thực sự là một công cụ mạnh mẽ để quản lý các dự án lớn, đặc biệt là khi làm việc nhóm.
Nên dùng cho case nào?
- Dự án lớn, phức tạp: Khi project của bạn có hàng trăm hoặc hàng ngàn class, nhiều gói và nhiều nhóm phát triển cùng làm việc.
- Phát triển thư viện, framework: Muốn cung cấp các API rõ ràng và ẩn đi các chi tiết triển khai nội bộ.
- Cần tối ưu kích thước runtime: Khi bạn muốn tạo các runtime image tùy chỉnh chỉ với những module cần thiết (ví dụ: với
jlink).
Không nên quá lạm dụng cho case nào?
- Dự án nhỏ, đơn giản: Đôi khi, việc thêm cấu trúc module có thể làm tăng độ phức tạp không cần thiết. Packages là đủ trong nhiều trường hợp.
Nhớ nhé, Modules không phải là 'viên đạn bạc' giải quyết mọi vấn đề, nhưng nó là một công cụ cực kỳ lợi hại trong 'hòm đồ nghề' của một lập trình viên Java chuyên nghiệp. Hãy 'thử nghiệm' và 'cảm nhận' sức mạnh của nó!
Thuộc Series: Java – OOP
Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!