Spring Core & Boot
11/41
Bài 11 / 41~12 phútContainer InternalsMiễn phí… lượt xem

Environment & PropertySource: Cơ Chế Cấu Hình Đa Nguồn

Khám phá Environment và PropertySource trong Spring, thứ tự ưu tiên cấu hình, cơ chế Relaxed Binding và phân biệt ${...} với SpEL #{...}.

TL;DR: Environment là lớp trừu tượng trung gian nằm giữa mã nguồn ứng dụng và toàn bộ nguồn cấu hình bên ngoài, phụ trách hai nhiệm vụ: quản lý thuộc tính (Properties) và kích hoạt cấu hình môi trường (Profiles). Các nguồn cấu hình được đóng gói thành các đối tượng PropertySource và sắp xếp theo một danh sách ưu tiên nghiêm ngặt: Command-line arguments luôn có quyền ưu tiên cao nhất, tiếp đến là JVM System Properties (-D), OS Environment Variables, và cuối cùng là các file application.properties. Khi phân giải placeholder, container duyệt tuần tự và dừng ngay khi tìm thấy giá trị đầu tiên.

Trong bài BeanDefinition & BFPP: Thao Tác Siêu Dữ Liệu Trước Khi Tạo Bean, chúng ta đã biết PropertySourcesPlaceholderConfigurer là một BFPP chịu trách nhiệm thay thế các placeholder cấu hình. Bài học này sẽ làm sáng tỏ cơ chế bên dưới của Environment và cách các nguồn cấu hình ghi đè lẫn nhau.

Giao diện Environment giải quyết bài toán trừu tượng hóa cấu hình ra sao?

Trước khi có Spring Framework, việc đọc cấu hình từ nhiều môi trường rất phân tán và dễ gây lỗi:

Java
// Cach doc truyen thong: ba API khac nhau khong co su thong nhat
String dbUrl = System.getProperty("db.url");
String apiKey = System.getenv("API_KEY");
String appName = bundle.getString("app.name");

Environment giải quyết vấn đề này bằng cách gom toàn bộ các nguồn cấu hình về một giao diện tập trung:

Java
// Spring Container tu dong tra cuu theo thu tu uu tien
String dbUrl = environment.getProperty("db.url");

Lập trình viên không cần quan tâm giá trị đến từ tham số dòng lệnh hay biến môi trường Docker; Container sẽ tự động xử lý và quyết định giá trị hợp lệ.

Cấu trúc PropertySource đóng gói các nguồn cấu hình như thế nào?

PropertySource là lớp trừu tượng đại diện cho một nguồn dữ liệu cụ thể. Spring Boot quản lý danh sách các nguồn này thông qua MutablePropertySources:

Bốn nguồn property xếp theo thứ tự tham số dòng lệnh, system property, biến môi trường, application.properties; nguồn nào có key trước thì trả giá trị và dừng ngay, không nguồn nào có thì trả về null

Khi phương thức environment.getProperty("db.url") được gọi, container duyệt tuần tự qua chuỗi PropertySource từ đầu đến cuối và dừng lại ngay lập tức khi tìm thấy giá trị đầu tiên khác null.

Thứ tự ưu tiên giữa các nguồn cấu hình được sắp xếp cụ thể ra sao?

Spring Boot áp dụng thứ tự ưu tiên nghiêm ngặt từ cao xuống thấp (nguồn ở trên sẽ ghi đè nguồn ở dưới):

Thứ tự ưu tiênNguồn cấu hìnhCách truyền thực tế
1 (Cao nhất)Command-line argumentsjava -jar app.jar --db.url=jdbc:postgresql://prod/app
2JVM System Propertiesjava -Ddb.url=jdbc:postgresql://prod/app -jar app.jar
3OS Environment Variablesexport DB_URL=jdbc:postgresql://prod/app
4Profile PropertiesFile application-prod.properties
5 (Thấp nhất)Default PropertiesFile application.properties đóng gói trong JAR

Cơ chế này tuân thủ nguyên lý của 12-Factor App: Cùng một file JAR build ra có thể triển khai lên Dev, Staging hay Production mà không cần biên dịch lại, chỉ cần ghi đè tham số qua biến môi trường hoặc dòng lệnh.

Cơ chế Relaxed Binding kết nối biến môi trường POSIX với Spring như thế nào?

Chuẩn POSIX của hệ điều hành quy định biến môi trường chỉ gồm chữ in hoa và dấu gạch dưới (APP_DATABASE_URL), trong khi quy ước của Spring dùng chữ thường và dấu chấm (app.database.url).

Cơ chế Relaxed Binding của Spring Boot tự động chuẩn hóa các định dạng tương đương:

Định dạngVí dụ cú phápNgữ cảnh sử dụng
Chuẩn kebab-caseapp.max-connectionsFile application.properties hoặc .yml
Chuẩn POSIX Env VarAPP_MAX_CONNECTIONSKubernetes ConfigMap / Docker Compose
Chuẩn camelCaseapp.maxConnectionsTên biến Java trong @ConfigurationProperties

Nhờ đó, lập trình viên có thể khai báo @Value("${app.max-connections}") trong mã nguồn Java, và Spring Boot vẫn nhận diện chính xác biến APP_MAX_CONNECTIONS được truyền vào từ container Docker.

Phân biệt bản chất giữa biểu thức Property Placeholder và cú pháp SpEL?

Annotation @Value hỗ trợ 2 cú pháp hoàn toàn khác nhau về bản chất:

Java
@Service
public class ConfigDemoService {
    // 1. Property Placeholder: Tra cuu qua Environment tu file/env var
    @Value("${db.pool-size:10}")
    private int poolSize;

    // 2. SpEL Expression: Danh gia bieu thuc logic va goi method
    @Value("#{systemProperties['user.home']}")
    private String userHome;

    @Value("#{T(java.lang.Runtime).getRuntime().availableProcessors()}")
    private int cpuCores;
}
Tiêu chíCú pháp Property PlaceholderCú pháp SpEL Expression
Cơ chế xử lýTra cứu chuỗi PropertySourceĐánh giá biểu thức qua SpEL Parser
Mục đích sử dụngĐọc cấu hình từ file, env var, command-lineThực thi logic động, truy cập Bean hoặc System class
Khả năng fallbackHỗ trợ cú pháp mặc định dạng hai chấmDùng toán tử bậc ba hoặc Elvis trong SpEL

Quản lý cấu hình đa môi trường bằng Profile hoạt động ra sao?

Profile cho phép tách biệt cấu hình và định nghĩa bean theo từng môi trường triển khai (dev, staging, prod):

Bash
# Kich hoat profile khi khoi dong ung dung
java -jar app.jar --spring.profiles.active=prod

Khi profile prod được kích hoạt, file application-prod.properties sẽ được nạp và có mức độ ưu tiên cao hơn application.properties mặc định.

Bạn cũng có thể giới hạn phạm vi của bean thông qua chú thích @Profile:

Java
@Configuration
@Profile("dev")
public class DevDatabaseConfig {
    @Bean
    public DataSource dataSource() {
        // Su dung co so du lieu in-memory cho moi truong dev
        return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build();
    }
}

Những cạm bẫy thực tế nào thường gặp khi cấu hình PropertySource?

  1. Nhầm lẫn thứ tự giữa JVM System Property và Env Var: Tham số -Ddb.url có mức ưu tiên cao hơn biến môi trường DB_URL. Nếu bạn đặt biến môi trường trong Kubernetes nhưng câu lệnh chạy lệnh chứa cờ -D, giá trị từ cờ -D sẽ luôn chiến thắng.
  2. Khai báo sai tên biến môi trường kích hoạt profile: Tên chuẩn phải là SPRING_PROFILES_ACTIVE. Việc đặt SPRING_PROFILES=prod sẽ không có tác dụng vì Relaxed Binding không thể map tới spring.profiles.active.
  3. Sử dụng @Value trong static field: Chú thích @Value chỉ hoạt động trên các trường của instance được Spring quản lý, hoàn toàn không hỗ trợ biến static.

Tóm tắt

  • Environment là giao diện trung tâm điều phối toàn bộ thuộc tính cấu hình và kích hoạt profile.
  • Chuỗi PropertySource xử lý ghi đè theo thứ tự: Command-line > System Property (-D) > Env Var > Profile file > Default file.
  • Relaxed Binding giúp xóa nhòa rào cản giữa biến môi trường viết hoa kiểu POSIX và thuộc tính Java kiểu kebab-case.
  • Sử dụng cú pháp placeholder cho các cấu hình ngoại hóa thông thường và chỉ dùng SpEL khi cần đánh giá biểu thức logic động.

Tự kiểm tra

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Một ứng dụng được cấu hình db.url ở 3 nguồn: file application.properties (localhost), biến môi trường DB_URL (staging), và cờ -Ddb.url (prod). Khi khởi động, giá trị nào sẽ được chọn?
  2. Q2
    Lợi ích quan trọng nhất của cơ chế Relaxed Binding trong Spring Boot khi triển khai trên Kubernetes/Docker là gì?
  3. Q3
    Sự khác biệt cốt lõi giữa việc dùng placeholder và dùng SpEL systemProperties là gì?
  4. Q4
    Để thiết lập giá trị mặc định fallback 5000 cho thuộc tính app.timeout, cách viết nào là chính xác?
  5. Q5
    Biến môi trường OS nào sau đây sẽ kích hoạt chính xác profile sản xuất 'prod' trong Spring Boot nhờ Relaxed Binding?

Bài tiếp theo: Resource & @SpringBootApplication: Cơ Chế Nạp Tài Nguyên

Bài này có giúp bạn hiểu bản chất không?

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Thảo luận & góp ý

Hỏi đáp về bài này

Bài tiếp theo

Resource & @SpringBootApplication: Cơ Chế Nạp Tài Nguyên