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.

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 + logWARN+ 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 chungThứ 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 và đè 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
@Profilelên@Component/@Service, lên@Beanlẻ, 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ổNoSuchBeanDefinitionExceptionlú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(+testcho CI là cùng).Secret vẫn không được vào file —
application-prod.ymlchứ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ấyif (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/prodvớiapp.greeting.messagekhá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:ConsoleNotifiercho!prod,EmailNotifier(giả lập) choprod— 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
Notifiernà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!
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.


