Spring Boot đọc cấu hình từ hơn mười nguồn. Bài này về thứ tự giữa chúng, in ra từ chính ứng dụng.

application.yml

app:
  ten: "dịch vụ đơn hàng"
  cong: 8080
  han-cho: 30s

YAML hoặc .properties đều được. YAML gọn hơn cho cấu hình phân cấp; properties ít cạm bẫy cú pháp hơn.

Quy ước tên: gạch nối, viết thường (han-cho). Spring có cơ chế "relaxed binding" nên hanCho, han_cho, HAN_CHO đều ánh xạ về cùng một thuộc tính — điều này quan trọng vì biến môi trường không dùng được gạch nối.

Thứ tự ưu tiên, in ra thật

   1. server.ports
   2. configurationProperties
   3. servletConfigInitParams
   4. servletContextInitParams
   5. systemProperties          (-D...)
   6. systemEnvironment         (biến môi trường)
   7. random
   8. application.yml

Danh sách này in bằng ((ConfigurableEnvironment) env).getPropertySources()nguồn đứng trước thắng.

Cộng thêm hai nguồn không hiện trong danh sách trên vì tôi không dùng: tham số dòng lệnh (--app.ten=x) đứng gần đầu, và application-{profile}.yml đứng ngay trên application.yml.

Kiểm bằng thực nghiệm:

  APP_TEN="từ biến môi trường" java -jar app.jar --spring.profiles.active=prod

  app.ten  = từ biến môi trường     <- biến môi trường thắng
  app.cong = 9090                    <- từ application-prod.yml

app.ten có ở cả ba nơi (application.yml, application-prod.yml, biến môi trường) và biến môi trường thắng. app.cong không có trong biến môi trường nên giá trị của profile thắng file gốc.

Chú ý cách viết biến môi trường: APP_TEN ánh xạ về app.ten — chữ hoa, gạch dưới thay dấu chấm.

Profile

java -jar app.jar --spring.profiles.active=prod
SPRING_PROFILES_ACTIVE=prod java -jar app.jar
  === PROFILE ĐANG BẬT ===
    [prod]

Profile làm hai việc:

Nạp thêm application-{profile}.yml, đè lên application.yml. Đây là hợp nhất, không phải thay thế — thuộc tính không khai lại vẫn giữ giá trị gốc.

Bật tắt bean:

@Component
@Profile("prod")
class GuiMailThat implements GuiMail { }

@Component
@Profile("!prod")
class GuiMailGia implements GuiMail { }

Đây là cách gọn nhất để thay cài đặt theo môi trường, và nó ghép với @Primary cùng @Qualifier ở bài 4.

Bật nhiều profile cùng lúc được: --spring.profiles.active=prod,asia. Cái sau đè cái trước.

Từ Boot 2.4, có thể gộp profile vào một tệp bằng ---spring.config.activate.on-profile — tiện cho dự án nhỏ, nhưng tệp riêng vẫn dễ đọc hơn khi cấu hình dài.

Chọn nguồn cho từng loại giá trị

Đây là phần thực dụng nhất:

Vào application.yml: giá trị mặc định hợp lý cho môi trường phát triển, thứ hiếm khi đổi, cấu trúc cấu hình.

Vào application-prod.yml: khác biệt theo môi trường mà không nhạy cảm — mức log, kích thước pool, tên dịch vụ.

Vào biến môi trường: mọi thứ khác nhau giữa các lần triển khai — địa chỉ CSDL, cờ tính năng.

Vào kho bí mật: mật khẩu, khoá API, chứng chỉ. Đừng để trong tệp cấu hình, kể cả tệp không commit.

Nguyên tắc từ Twelve-Factor App: cấu hình khác nhau giữa các môi trường thì nằm ngoài mã, còn cấu trúc thì nằm trong.

@Value và giá trị mặc định

@Value("${app.ten}") String ten;
@Value("${app.khong-co:giá trị mặc định}") String thieu;
  ten=từ application.yml | thiếu -> giá trị mặc định

Dấu hai chấm khai mặc định. Không có mặc định mà thuộc tính thiếu thì ứng dụng không khởi động được — và đó là hành vi đúng, giống nguyên tắc "chết ngay nếu thiếu" ở bài 56 sê-ri Go.

Nhưng @Value có ba nhược điểm, và bài mai sẽ nói về cách thay thế tốt hơn.

Đừng đọc cấu hình rải rác

@Value("${app.han-cho}") String hanCho;   // ở lớp A
@Value("${app.han-cho}") String hanCho2;  // ở lớp B

Cùng một thuộc tính khai ở nhiều nơi là chỗ để lệch nhau. Gom vào một lớp cấu hình — bài mai.

Bí mật

Đừng commit bí mật, kể cả trong application-dev.yml. Thêm application-local.yml vào .gitignore cho giá trị cá nhân.

Đừng log toàn bộ cấu hình. Endpoint /actuator/env lộ mọi thuộc tính — bài 10 sẽ nói cách khoá nó lại.

Spring Boot có che một phần: thuộc tính có tên chứa password, secret, key, token sẽ hiện thành ****** trong actuator. Nhưng nó dựa vào tên, nên app.mat-khau không được che.

Thử ba mươi giây

var src = ((ConfigurableEnvironment) env).getPropertySources();
src.forEach(s -> System.out.println(s.getName()));

In ra thứ tự nguồn cấu hình thật của ứng dụng bạn. Lần tới khi một giá trị "không chịu đổi", danh sách này cho biết nguồn nào đang thắng — thay vì đoán.

Ngày mai: @ConfigurationProperties — gom cấu hình thành đối tượng có kiểu.