Backend

99 Ngày Spring — Ngày 09: Profiles — cấu hình theo môi trường

SSite Admin
6 tháng 08, 2026 5 phút đọc 28 lượt xem
99 Ngày Spring — Ngày 09: Profiles — cấu hình theo môi trường

Ngày 08 khép lại bằng một câu hỏi bỏ ngỏ: đổi một giá trị bằng env var thì dễ rồi, nhưng dev cần H2 còn production cần PostgreSQL — cả cụm cấu hình lẫn bean phải đổi theo môi trường thì sao? Câu trả lời của Spring là Profiles — bộ công tắc môi trường: dán nhãn cho từng khối config và từng bean (dev, prod…), rồi chỉ cần gạt spring.profiles.active là cả ứng dụng "thay bộ đồ" đúng môi trường.

Sketchnote Ngày 09: Spring Profiles — config theo môi trường, @Profile và cách kích hoạt

Vì sao cần profile?

  • Mỗi môi trường một bộ đồ nghề: dev muốn H2 + log DEBUG + dữ liệu mẫu; production muốn PostgreSQL + log WARN + tuyệt đối không seed bậy.

  • Env var của Ngày 08 đổi từng giá trị lẻ rất giỏi — nhưng chuyển môi trường là đổi hàng chục giá trị + vài bean cùng lúc, cần một công tắc gộp.

  • Một bản build duy nhất vẫn là luật (12-factor): JAR không đổi, chỉ nhãn môi trường được gạt lúc chạy.

Config theo môi trường: on-profile & application-{profile}.yml

# application.yml — phần CHUNG, môi trường nào cũng nạpspring:application:
    name: blog

---                              # '---' mở một "tài liệu" mới trong CÙNG filespring:config:
    activate:
      on-profile: dev            # đoạn dưới đây CHỈ nạp khi profile "dev" bậtdatasource:
    url: jdbc:h2:mem:blog        # dev xài H2 trong RAM cho nhẹ

---spring:config:
    activate:
      on-profile: proddatasource:
    url: jdbc:postgresql://db:5432/blog    # production xài PostgreSQL thật

# Cách 2 tương đương: tách file application-dev.yml / application-prod.yml# — Boot tự nạp file khớp tên profile đang bật, ĐÈ lên application.yml chung

Thứ tự nạp rất dễ nhớ: phần chung nạp trước, phần của profile đang bật nạp sauđè lên — đúng tinh thần "nguồn sau đè nguồn trước" của Ngày 08. Dự án nhỏ gọn thì multi-document một file cho dễ nhìn tổng thể; config phình to thì tách application-dev.yml / application-prod.yml cho ai lo môi trường nấy.

@Profile — bean theo môi trường

Profile không dừng ở giá trị cấu hình — nó chọn được cả bean (Ngày 05–06 dùng lại ngay):

// Bean chỉ tồn tại ở môi trường được chỉ định@Configuration@Profile("dev")public class DevDataConfig {
    @Bean
    CommandLineRunner seedData(PostRepository repo) {  // dữ liệu mẫu cho dev
        return args -> repo.save(new Post("Bài viết thử"));
    }
}

// Hai bản cài đặt của CÙNG một interface — profile quyết định bản nào sống:@Service@Profile("prod")public class S3StorageService implements StorageService { }

@Service@Profile("!prod")                // '!' = mọi môi trường TRỪ prodpublic class LocalStorageService implements StorageService { }

// Nơi khác cứ tiêm StorageService (Ngày 04) — không cần biết đang ở môi trường nào
  • Đặt @Profile lên @Component/@Service, lên @Bean lẻ, hoặc cả lớp @Configuration — bean không khớp profile coi như không tồn tại, container bỏ qua từ vòng gửi xe.

  • Cú pháp biểu thức: !prod (phủ định), dev | test (hoặc), prod & metrics (và).

  • Cặp @Profile("prod") / @Profile("!prod") trên cùng interface là bài thay thế bean theo môi trường kinh điển — nhớ phủ kín mọi trường hợp, kẻo môi trường nào đó không có bean nào và app nổ NoSuchBeanDefinitionException lúc khởi động.

Kích hoạt profile

# 1. Trong application.yml — chỉ nên dùng làm mặc định cho dev local:spring:profiles:
    active: dev

# 2. Biến môi trường — cách chuẩn cho server (relaxed binding Ngày 08!):SPRING_PROFILES_ACTIVE=prod java -jar blog.jar

# 3. Tham số dòng lệnh — ưu tiên cao nhất, đè tất cả:java -jar blog.jar --spring.profiles.active=staging

# Bật NHIỀU profile cùng lúc (phải, qua trái đè):SPRING_PROFILES_ACTIVE=prod,metrics

# Không bật gì cả? Spring chạy với profile tên là "default"

Ba nấc quen thuộc từ chuỗi ưu tiên Ngày 08. Thực chiến phổ biến nhất: file yml đặt active: dev cho máy dev chạy phát ăn ngay, còn server do vận hành gạt SPRING_PROFILES_ACTIVE — Dockerfile, Kubernetes manifest hay dashboard hosting đều có chỗ điền env var này. Kiểm tra profile nào đang bật? Dòng log đầu tiên khi Boot khởi động: The following 1 profile is active: "dev".

Profile group & vài lời khuyên

# Profile group — bật 1 công tắc tổng, cả cụm công tắc con bật theospring:profiles:
    group:
      prod: "proddb, prodcache, metrics"

# SPRING_PROFILES_ACTIVE=prod  →  proddb + prodcache + metrics cùng bật# → tách config theo CHỨC NĂNG nhỏ, nhưng vận hành chỉ cần nhớ MỘT tên
  • Đừng đẻ profile vô tội vạ — mỗi profile mới nhân đôi số tổ hợp cần test. Đa số dự án chỉ cần dev / prod (+ test cho CI là cùng).

  • Secret vẫn không được vào fileapplication-prod.yml chứa cấu trúc của production (URL, pool size), còn mật khẩu đi đường env var / secret manager như Ngày 08 đã dặn.

  • Code nghiệp vụ không nên hỏi "đang ở profile nào?" (Environment.getActiveProfiles()) — hãy để profile chọn bean, còn logic thì viết trung lập; nếu thấy if (isProd) rải rác là mùi thiết kế.

  • Trong test, @ActiveProfiles("test") ép profile cho từng lớp test — gặp lại ở giai đoạn Testing (Ngày 61+).

Bài tập nhỏ

  • Tạo 2 profile dev/prod với app.greeting.message khác nhau (multi-document một file) — chạy lần lượt từng profile và xem giá trị đổi.

  • Viết 2 bean Notifier: ConsoleNotifier cho !prod, EmailNotifier (giả lập) cho prod — tiêm interface và xác nhận bean đổi theo profile.

  • Kích hoạt bằng cả 3 cách (yml, env var, dòng lệnh) — xác nhận thứ tự ưu tiên bằng dòng log profile is active.

  • Cố tình chạy profile không có bean Notifier nào — đọc kỹ NoSuchBeanDefinitionException để nhận diện lỗi phủ profile thiếu.

Kết luận

Bức tranh cấu hình đã hoàn chỉnh: Ngày 08 đưa giá trị ra ngoài code, hôm nay Profiles gom chúng thành bộ đồ theo môi trường — config đè theo profile, bean chọn theo @Profile, kích hoạt bằng một công tắc spring.profiles.active. Nhưng @Profile thực ra chỉ là trường hợp đặc biệt của một cơ chế mạnh hơn nhiều: Ngày 10 ta mở nắp @Conditional — trái tim của toàn bộ auto-configuration, lời giải cho câu hỏi treo từ Ngày 02: "vì sao Boot tự cấu hình được?". Hẹn gặp lại!

S

Site Admin

Engineer and writer. Building things with TypeScript and distributed systems.

Bình luận (0)

Bạn cần đăng nhập bằng Google để bình luận.

Hãy là người bình luận đầu tiên.

Bài viết liên quan

99 Ngày Spring — Ngày 14: Bean Validation — @Valid & bộ constraint chuẩn

Ngày 14 của 99 Ngày Spring: Bean Validation khai báo luật ngay trên DTO — @Valid kích hoạt, bộ constraint chuẩn (@NotBlank vs @NotEmpty vs @NotNull, @Size, @Email, @Min/@Max), bẫy int vs Integer, validate object lồng nhau không tự lan, và lỗi 400 gom một lượt.

10 thg 8, 20265 phút0
99 Ngày Java — Ngày 14: Kế thừa (Inheritance)

Ngày 14 của 99 Ngày Java: extends trao gia tài từ lớp cha, override với @Override và super., thứ tự khai sinh cha trước con sau qua super(...), protected trả nợ Ngày 13, và phép thử is-a vs has-a — khi nào nên composition thay vì kế thừa.

10 thg 8, 20266 phút0
99 Ngày Spring — Ngày 13: @RequestBody & DTO — đừng công khai entity ra API

Ngày 13 của 99 Ngày Spring: @RequestBody + Jackson deserialize JSON thành object, DTO là hợp đồng API — chặn lỗ hổng mass assignment chiều vào và rò rỉ dữ liệu chiều ra, request/response DTO khác nhau là bình thường, và Jackson annotations tinh chỉnh hợp đồng JSON.

9 thg 8, 20266 phút9