Vert.x không có application.properties tự động như Spring Boot. Cấu hình là một module riêng, bạn khai rõ lấy từ đâu — đổi lại là kiểm soát hoàn toàn, và một cái bẫy im lặng.

Nhiều nguồn, gộp lại theo thứ tự khai báo

var opts = new ConfigRetrieverOptions()
    .addStore(new ConfigStoreOptions().setType("file").setFormat("json")
              .setConfig(new JsonObject().put("path", "ung-dung.json")))
    .addStore(new ConfigStoreOptions().setType("sys"));       // thuoc tinh he thong

Tệp khai ten = "tu-tep", thuộc tính hệ thống khai ten = "tu-thuoc-tinh-he-thong":

kết quả: ten = tu-thuoc-tinh-he-thong        -> nguồn khai SAU thắng
chỉ-có-ở-tệp vẫn còn? true                   -> gộp chứ không thay thế

Đảo thứ tự hai addStore:

kết quả: ten = tu-tep

Hai luật, và cả hai đều đơn giản: nguồn khai sau ghi đè nguồn khai trước, và những khoá chỉ có ở một nguồn thì vẫn còn nguyên — đây là phép gộp, không phải phép thay thế.

Thứ tự thực dụng: tệp mặc định trước, rồi tệp theo môi trường, rồi biến môi trường, rồi thuộc tính hệ thống. Cái nào cụ thể hơn thì khai sau.

Nạp nóng chạy tốt — nếu bạn khai một dòng

var opts = new ConfigRetrieverOptions()
    .setScanPeriod(1000)                     // DONG NAY
    .addStore(...);

cr.listen(ch -> capNhat(ch.getNewConfiguration()));
scanPeriod = 1 000 ms -> phát hiện thay đổi sau 499 ms
giá trị mới: ten = da-doi

Sửa tệp, và trong vòng nửa giây listen chạy với cấu hình mới. Chính xác và nhanh hơn tôi tưởng — 499 ms với chu kỳ quét 1 giây, tức nó bắt được ngay ở lần quét kế tiếp.

Và đây là cái bẫy

Bỏ đúng một dòng setScanPeriod, giữ nguyên mọi thứ khác:

sau 3 giây, số lần listen được gọi: 0
giá trị trong bộ nhớ: (vẫn là giá trị cũ)
Không có scanPeriod, ConfigRetriever không quét lại bao giờ. listen() vẫn nhận đăng ký của bạn, không ném lỗi, không cảnh báo — nó chỉ không bao giờ chạy. Mã biên dịch được, chạy được, và tính năng "cấu hình nóng" của bạn đơn giản là không tồn tại.

Đây là kiểu lỗi tệ nhất vì nó âm thầm và trông giống như đã xong việc. Cách duy nhất phát hiện là thử sửa tệp và xem listener có chạy không — đúng phép thử ở cuối bài.

Chọn nguồn nào

Kiểu store Dùng khi
file (json, yaml, properties) Giá trị mặc định đi cùng ứng dụng
env Biến môi trường — chuẩn de facto cho container
sys Thuộc tính hệ thống, tiện để ghi đè lúc chạy thử
http Máy chủ cấu hình tập trung
kubernetes ConfigMap và Secret, đọc thẳng từ API

Với httpkubernetes thì scanPeriod mới thật sự đáng giá: cấu hình đổi ở nơi khác và ứng dụng tự nhận, không cần khởi động lại.

Nhưng cân nhắc kỹ cái gì nên nạp nóng được. Đổi ngưỡng cảnh báo hay mức log lúc chạy thì tốt; đổi chuỗi kết nối CSDL lúc chạy thì bạn phải tự lo chuyện đóng pool cũ, và không có gì trong Vert.x làm hộ. Nạp nóng cho bạn giá trị mới, không cho bạn cách áp dụng nó an toàn.

Ba điều nên làm

Khai scanPeriod nếu bạn dùng listen. Không có nó thì listen là mã chết.

Đặt nguồn cụ thể nhất ở cuối. Bảng đầu bài là luật, và nó ngược với trực giác của một số người quen với thứ tự ưu tiên "cái đầu tiên thắng".

Kiểm tra cấu hình lúc khởi động. Vert.x Config trả về JsonObject trần — không có kiểu, không có validation, không báo lỗi khi thiếu khoá. config.getString("db.url") trả null và ứng dụng cứ thế chạy tiếp.

Bài sau: health check và readiness — hai thứ hay bị khai nhầm cho nhau, và cái giá lúc triển khai.

Thử ba mươi giây

cr.listen(ch -> System.out.println("cau hinh doi luc " + System.currentTimeMillis()));
// roi sua tep cau hinh

Nếu sửa tệp mà không thấy dòng nào in ra, bạn đang ở đúng cái bẫy ở trên — và tính năng nạp nóng mà bạn tưởng đã có thật ra chưa bao giờ chạy.