1. Vấn đề của cách tiếp cận cũ
“Classpath party”: khi mọi thứ ở chung một chỗ
Trước khi có mô-đun trong Java (ở phiên bản 9), toàn bộ mã ứng dụng, thư viện và phụ thuộc đều đổ vào một đống lớn — classpath. Gần như không có ranh giới giữa các thư viện: bất kỳ class nào nằm trong classpath đều có thể được tìm thấy và sử dụng bởi bất kỳ ai.
Xảy ra xung đột tên: hai thư viện chứa class có tên đầy đủ giống nhau, ví dụ com.example.Util — hệ thống chọn “một cái bất kỳ”, và bạn chỉ phát hiện vấn đề qua hành vi kỳ lạ lúc chạy.
Kinh điển — dependency hell: một thư viện yêu cầu logger phiên bản 1.2, thư viện khác — 1.3, và trong classpath lại có cả hai. JVM không biết phải nạp cái nào, và bạn gặp lỗi bí hiểm NoSuchMethodError.
Thêm nữa: ngay cả khi class được khai báo public nhưng chỉ dùng nội bộ thư viện, mã khác vẫn có thể sử dụng nó — và có thể tự làm hỏng thứ gì đó trong quá trình đó. Dự án lớn biến thành bãi mìn, còn bảo trì trở thành một cuộc phiêu lưu.
2. Mô-đun trong Java là gì?
Mô-đun: như một chiếc hộp có lỗ
Trong Java 9 xuất hiện hệ thống mô-đun (JPMS). Mô-đun là một chiếc hộp chứa các class và package, nơi bạn tự “đục lỗ”: chỉ rõ cái gì công khai ra ngoài (exports) và cái gì giữ bên trong. Và đúng là: ngay cả class public cũng không được mô-đun khác nhìn thấy nếu package của nó không được export.
Định nghĩa chính thức
Mô-đun là đơn vị logic để chia nhỏ mã, gom các package và class có liên quan. Mỗi mô-đun khai báo rõ:
- những gì nó export — làm cho mô-đun khác truy cập được (exports);
- và những gì nó nhập/phụ thuộc — nó phụ thuộc vào mô-đun nào khác (requires).
Thuộc tính cốt lõi của mô-đun:
- Ranh giới tường minh. Xác định rõ cái gì có thể truy cập từ bên ngoài, cái gì không.
- Phụ thuộc tường minh. Mô-đun không “nhìn thấy” mô-đun khác nếu không có requires rõ ràng.
- Cô lập. Chi tiết nội bộ có thể ẩn hoàn toàn khỏi bên ngoài.
3. Lợi ích của tính mô-đun
Tăng cường đóng gói (encapsulation)
Có thêm một cấp độ phạm vi — cấp mô-đun. Ngay cả khi class là public nhưng package của nó không được export, nó chỉ nhìn thấy bên trong mô-đun. Nội bộ thư viện không còn “rò rỉ” ra ngoài.
Mô tả phụ thuộc tường minh
Các phụ thuộc cần thiết được liệt kê trong module-info.java. Trình biên dịch và IDE sẽ báo trước nếu bạn quên chỉ ra mô-đun mà bạn phụ thuộc vào.
Bảo mật và độ tin cậy tốt hơn
Hạn chế truy cập vào API nội bộ làm khó cả vô tình lẫn cố ý can thiệp. Điều này đặc biệt quan trọng cho thư viện lớn và mô-đun nền tảng.
Khả năng tạo JRE “tối giản”
Với jlink bạn có thể lắp một runtime tối thiểu chỉ từ các mô-đun cần thiết. Trong môi trường đám mây và embedded, điều này tiết kiệm dung lượng đĩa và bộ nhớ.
Bonus: khởi động nhanh hơn và nhỏ gọn hơn
Không phải tất cả mô-đun đều được nạp, chỉ những mô-đun thực sự được dùng — ứng dụng khởi động nhanh hơn và dùng ít tài nguyên hơn.
4. Mô-đun được dùng ở đâu
Trong thư viện chuẩn của Java
Từ Java 9, bản thân nền tảng đã mô-đun. Ví dụ:
- java.base — mô-đun cơ bản (bắt buộc cho mọi chương trình).
- java.sql — làm việc với cơ sở dữ liệu.
- java.xml — làm việc với XML.
Nếu bạn không dùng XML, mô-đun java.xml thậm chí sẽ không đi vào runtime của bạn.
Trong ứng dụng và thư viện lớn
Là “must-have” cho hệ thống doanh nghiệp, nơi hàng chục nhóm phát triển các phần của một monorepo. Tính mô-đun giảm xung đột và đơn giản hóa bảo trì.
Trong dự án của chính bạn
Ngay cả với pet project, mô-đun giúp rèn luyện tư duy kiến trúc và giữ mã gọn gàng, tránh “spaghetti code”.
5. Tổng quan nhanh về cú pháp: module-info.java
Điều quan trọng nhất — tệp module-info.java
Tệp module-info.java nằm ở gốc mã nguồn của mô-đun và khai báo ranh giới cũng như các phụ thuộc của nó.
module my.awesome.module {
exports com.example.api; // Gói được export
requires java.sql; // Phụ thuộc vào mô-đun chuẩn
}
Các từ khóa chính:
- module <tên> — khai báo mô-đun.
- exports <gói> — làm cho package có thể truy cập từ các mô-đun khác.
- requires <mô-đun> — khai báo phụ thuộc vào mô-đun khác.
Ví dụ tối thiểu:
module com.myproject.core {
exports com.myproject.core.api;
}
Ví dụ có phụ thuộc:
module com.myproject.app {
requires com.myproject.core;
requires java.sql;
}
Khả năng bổ sung (ngắn gọn): cho reflection có opens, cho services có uses và provides ... with ... (chi tiết — trong các bài học nâng cao).
6. Những lưu ý hữu ích
Mô-đun giống như “hộ chiếu” cho các class. Trước đây, bất kỳ class nào có “visa” public đều có thể “du lịch” khắp ứng dụng. Giờ còn cần “hộ chiếu mô-đun” — export package (exports). Không có nó, class sẽ “ở nhà”.
Dependency hell là thuật ngữ có thật. Nếu từng thấy ClassNotFoundException: com.google.common.base.Strings, bạn đã ghé thăm nơi đó. Mô-đun sinh ra để xua những “con quỷ” này bằng ranh giới và phụ thuộc nghiêm ngặt.
Toàn bộ Java giờ đã mô-đun. Dù bạn không tự viết mô-đun, nền tảng đã chia thành hàng chục mô-đun. Hãy thử lệnh:
java --list-modules
Điều này ảnh hưởng thế nào đến phát triển
Bạn kiểm soát rõ ràng phạm vi hiển thị của mã, và các package nội bộ không còn vô tình “mở” cho tất cả. IDE và trình biên dịch bắt lỗi sớm hơn: quên khai báo phụ thuộc — dự án sẽ không build. Việc bảo trì hệ thống lớn đơn giản hơn: dễ hiểu ai phụ thuộc vào ai và điều gì sẽ vỡ khi thay đổi.
7. Những lỗi thường gặp khi chuyển sang mô-đun
Lỗi số 1: quên export package, nhưng class là public. Bạn khai báo class là public nhưng không export package của nó — các mô-đun khác sẽ không thể sử dụng. Trình biên dịch sẽ báo: “Package không được mô-đun export”.
Lỗi số 2: không khai báo phụ thuộc qua requires. Bạn dùng một kiểu từ mô-đun khác nhưng quên thêm requires vào module-info.java. Kết quả — lỗi biên dịch “mô-đun không thể tìm thấy class cần thiết”.
Lỗi số 3: trùng tên mô-đun. Trong một dự án lớn, hai mô-đun vô tình có cùng tên. JVM không chấp nhận điều này — hãy đổi tên và tuân thủ quy tắc đặt tên thống nhất.
Lỗi số 4: quên các mô-đun chuẩn. Ví dụ, bạn dùng JDBC nhưng không thêm requires java.sql;. Ở Java 8 “tự thấy được”, còn ở 9+ — thì không.
Lỗi số 5: cố dùng class nội bộ của mô-đun khác. Nếu package không được export, ngay cả class public cũng vẫn vô hình từ bên ngoài. Hãy export package hoặc tách API ra “lớp” công khai.
GO TO FULL VERSION