Spring Core & Boot
35/41
Bài 35 / 41~12 phútConfig, Profiles & LoggingLộ trình · chặng 14/21Miễn phí… lượt xem

PropertySource Ordering: Thứ Tự Ưu Tiên Cấu Hình Spring

Khám phá thứ tự ưu tiên các nguồn cấu hình PropertySource trong Spring Boot và cơ chế relax binding chuyển đổi biến môi trường K8s.

TL;DR: Spring Boot xếp chồng nhiều PropertySource theo thứ tự ưu tiên cố định: command line args đứng đầu, kế đến biến môi trường OS, file profile application-{profile}.yml, và cuối cùng là application.yml. Khi tìm khóa cấu hình, Spring tra từ trên xuống và lấy ngay giá trị đầu tiên tìm thấy. Tính năng relax binding tự động ánh xạ tên biến UPPER_SNAKE_CASE từ Kubernetes thành kebab-case trong file cấu hình mà không cần khai báo bổ sung.

Nhớ lại — Spring Boot là gì

Tại sao cần tách cấu hình khỏi mã nguồn theo 12-factor?

Trong các hệ thống Spring truyền thống, thông tin kết nối thường bị gán cứng vào mã nguồn:

Java
// Hardcode connection parameters directly in code
DataSource ds = new HikariDataSource(
    "jdbc:postgresql://localhost/dev",
    "dev_user",
    "dev_pass"
);

Cách làm này tạo ra ba rào cản nghiêm trọng trong vận hành:

  1. Không thể tái sử dụng artifact: Mỗi khi chuyển từ dev sang staging hay production, đội ngũ phải sửa mã nguồn và build lại file jar mới.
  2. Dễ lộ lọt bí mật hệ thống: Mật khẩu và khóa bí mật nằm trong git history rất dễ bị lộ lọt khi kho mã nguồn bị truy cập trái phép.
  3. Khó khăn khi ghi đè runtime: Muốn kiểm tra khẩn cấp một tham số tạm thời đòi hỏi toàn bộ quy trình build và deploy lặp lại từ đầu.

Nguyên lý 12-factor App (yếu tố III - Config) quy định: mọi thông số thay đổi giữa các môi trường triển khai phải được tách khỏi mã nguồn và nạp từ bên ngoài. Một file jar duy nhất phải vận hành được trên mọi máy chủ:

Bash
# Single artifact runs across multiple environments
java -jar app.jar --spring.profiles.active=dev
DB_URL=jdbc:postgresql://prod/app java -jar app.jar

Spring Boot hiện thực hóa nguyên lý này thông qua mô hình phân tầng PropertySource.

Spring Boot giải quyết xung đột thứ tự PropertySource như thế nhau?

Giao diện Environment của Spring quản lý danh sách các PropertySource theo một trật tự xác định. Khi cùng một khóa xuất hiện tại nhiều nơi, nguồn có độ ưu tiên cao hơn sẽ quyết định giá trị.

Thứ tựNguồn cấu hìnhVí dụ cú phápNgữ cảnh sử dụng
1 (Cao nhất)Command line arguments--db.url=jdbc:...Ghi đè tạm thời, smoke test của SRE
2Java System Properties-Ddb.url=jdbc:...Thiết lập mức tiến trình JVM
3OS Environment VariablesDB_URL=jdbc:...Inject từ Kubernetes Secret / Docker
4Profile-specific fileapplication-prod.ymlCấu hình chuyên biệt từng môi trường
5 (Thấp nhất)Default application fileapplication.ymlCấu hình gốc và giá trị mặc định cho local dev

Quy tắc ghi nhớ: Nguồn càng gần thời điểm thực thi do người vận hành chỉ định thì độ ưu tiên càng cao.

Bốn nguồn cùng set db.url; resolver dừng ngay ở command line args và trả về prod2, ba giá trị còn lại ở tầng dưới không được đọc tới

Bộ xử lý PropertySourcesPropertyResolver duyệt qua danh sách nguồn tuần tự. Ngay khi tìm thấy giá trị hợp lệ đầu tiên, nó lập tức trả về kết quả mà không quét tiếp các tầng bên dưới. Bạn có thể kiểm tra nguồn gốc thắng cuộc tại runtime qua endpoint actuator:

JSON
{
  "name": "db.url",
  "property": {
    "value": "jdbc:postgresql://prod/app",
    "origin": "System Environment Property \"DB_URL\""
  }
}

Tại sao command line args có quyền ưu tiên cao nhất?

Command line arguments được trao quyền cao nhất vì đây là quyết định can thiệp trực tiếp của kỹ sư vận hành tại thời điểm chạy ứng dụng:

Bash
java -jar app.jar --db.url=jdbc:postgresql://canary/app

Khi gặp sự cố khẩn cấp trên cụm Kubernetes, kỹ sư vận hành có thể cần trỏ tạm thời một Pod về cơ sở dữ liệu dự phòng mà không muốn cập nhật manifest của toàn bộ Deployment:

Bash
kubectl exec pod-app -- java -jar app.jar --db.url=jdbc:postgresql://backup/app

Nếu biến môi trường được ưu tiên hơn command line, kỹ sư sẽ bị tước quyền ghi đè tức thời này và buộc phải thay đổi tài nguyên Kubernetes chung, gây ảnh hưởng diện rộng.

Cơ chế relax binding chuyển đổi tên biến K8s ra sao?

Hệ điều hành Unix và các hệ thống container như Docker, Kubernetes quy ước biến môi trường ở dạng UPPER_SNAKE_CASE (SPRING_DATASOURCE_URL). Ngược lại, Spring Boot định nghĩa cấu hình file YAML theo phong cách kebab-case (spring.datasource.url).

Spring Boot giải quyết khác biệt này bằng cơ chế relax binding. Trước khi đối soát, hệ thống lược bỏ toàn bộ dấu chấm, dấu gạch nối, dấu gạch dưới và đưa chuỗi về dạng chữ thường duy nhất:

Plain Text
spring.datasource.url   -> springdatasourceurl
SPRING_DATASOURCE_URL   -> springdatasourceurl
spring_datasource_url   -> springdatasourceurl

Ba cách viết cùng cho ra canonical springdatasourceurl nên bind vào cùng một property; còn env var thiếu segment HIKARI cho canonical khác nên không bind mà cũng không sinh lỗi

Nhờ dạng chuẩn hóa (canonical form) trùng khớp, Spring Boot tự động kết nối giá trị biến môi trường vào thuộc tính mà nhà phát triển không cần viết bất kỳ đoạn mã điều phối nào.

YAML
# K8s manifest maps to spring.datasource.url via relaxed binding
spec:
  containers:
    - name: app
      env:
        - name: SPRING_DATASOURCE_URL
          value: jdbc:postgresql://prod/app
        - name: SPRING_DATASOURCE_USERNAME
          value: app_prod

Chiến lược quản trị cấu hình production chuẩn gồm những gì?

Một quy trình quản lý cấu hình đạt chuẩn an toàn bao gồm sự phối hợp giữa file mã nguồn và biến môi trường:

YAML
# application.yml: tracked in git, contains defaults for local dev
spring:
  datasource:
    url: jdbc:postgresql://localhost/dev
    username: dev_user
    password: <code>{'${DB_PASSWORD:dev_pass}'}</code>

# K8s deployment: Inject secrets from Secret store
spec:
  containers:
    - name: api
      env:
        - name: SPRING_PROFILES_ACTIVE
          value: prod
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-credentials
              key: password

Trong mô hình này:

  • Lưu trữ trong git: File application.yml chứa giá trị mặc định cho local dev và application-prod.yml chứa các tham số không nhạy cảm như pool size, timeout.
  • Cung cấp tại runtime: Nạp mật khẩu, access token và connection string qua biến môi trường lấy từ Kubernetes Secret hoặc HashiCorp Vault.

Những lỗi sai kinh điển nào thường gặp khi cấu hình môi trường?

Dưới đây là các tình huống khiến hệ thống không nhận cấu hình như kỳ vọng:

  1. Bị command line ghi đè ngầm: Đặt biến môi trường SPRING_PROFILES_ACTIVE=prod nhưng script khởi động trong Dockerfile lại chứa cờ --spring.profiles.active=dev. Vì command line ưu tiên cao hơn, cấu hình production bị vô hiệu hóa.
  2. Thiếu phân đoạn (segment) trong tên biến môi trường: Khai báo SPRING_DATASOURCE_MAXIMUMPOOLSIZE=20 sẽ không thể gắn vào spring.datasource.hikari.maximum-pool-size vì thiếu mất từ khóa HIKARI. Tên chính xác phải là SPRING_DATASOURCE_HIKARI_MAXIMUM_POOL_SIZE.
  3. Khai báo file YAML với annotation @PropertySource: Annotation @PropertySource("classpath:custom.yml") mặc định chỉ đọc file định dạng .properties. Muốn nạp YAML tùy biến, bạn phải định nghĩa PropertySourceFactory riêng biệt.

Tóm tắt

  • Nguyên lý 12-factor Config: Giữ mã nguồn độc lập với môi trường chạy; nạp thông số qua biến môi trường để một file jar dùng chung cho toàn bộ hạ tầng.
  • Thứ tự phân tầng: Command line arguments > Java System Properties > OS Environment Variables > Profile YAML > Default YAML.
  • Quy tắc giải quyết: PropertySourcesPropertyResolver quét tuần tự từ trên xuống dưới và ngắt ngay khi tìm thấy giá trị hợp lệ đầu tiên.
  • Relax binding: Chuẩn hóa tên thuộc tính về dạng chữ thường không ngăn cách, giúp tương thích hoàn hảo giữa shell container và file YAML.
  • Bảo mật: Tuyệt đối không commit thông tin nhạy cảm vào repository; luôn sử dụng placeholder kết hợp biến môi trường runtime.

Tự kiểm tra

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Ứng dụng đồng thời nhận 4 nguồn cấu hình cho db.url: application.yml (localhost), OS env DB_URL (staging), JVM flag -Ddb.url (prod1), và command line --db.url (prod2). Giá trị nào sẽ được chọn?
  2. Q2
    Vì sao các tham số dòng lệnh (command line args) lại được thiết kế có quyền ưu tiên cao hơn biến môi trường OS?
  3. Q3
    Cơ chế nào giúp Spring Boot tự động nhận diện biến SPRING_DATASOURCE_URL và gán vào thuộc tính spring.datasource.url?
  4. Q4
    Để ghi đè thuộc tính spring.datasource.hikari.maximum-pool-size qua biến môi trường OS, tên biến chuẩn xác là gì?
  5. Q5
    Theo chuẩn quản trị cấu hình 12-factor, thông tin mật khẩu cơ sở dữ liệu production nên được quản lý như thế nào?

Bài tiếp theo: @ConfigurationProperties vs @Value

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.

Lộ trình Junior Java Backend Developer

Chặng 14/21 · Spring Boot & autoconfiguration — bài 10/16 của chặng

Thảo luận & góp ý

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

Bài tiếp theo

@ConfigurationProperties vs @Value: Type-Safe Binding