Spring Profiles: Quản Lý Môi Trường Với 1 Artifact
Khám phá cách Spring Profiles gắn nhãn cấu hình và bean theo môi trường triển khai, 5 phương thức kích hoạt và cơ chế đánh giá điều kiện.
TL;DR: Spring Profiles là cơ chế gắn nhãn (named tags) giúp tách biệt cấu hình và định nghĩa bean theo từng môi trường triển khai. Khi kích hoạt profile như prod, container chỉ nạp các bean khớp điều kiện và đọc file application-prod.yml, đồng thời bỏ qua toàn bộ định nghĩa không phù hợp. Điều kiện @Profile được đánh giá ngay tại pha đăng ký BeanDefinition, giúp phát hiện lỗi cấu hình sớm ngay lúc khởi động.
Tại sao profile giúp loại bỏ anti-pattern đa môi trường?
Trước khi có cơ chế profile, các dự án phần mềm thường vướng vào hai lỗi thiết kế kinh điển:
- Đóng gói riêng từng môi trường: Dùng profile của Maven/Gradle để tạo ra các file jar khác nhau (
app-dev.jar,app-prod.jar). Cách này dẫn đến rủi ro bản kiểm thử thành công ở dev lại khác với bản chạy thực tế trên production. - Kiểm tra môi trường bằng câu lệnh điều kiện: Viết lệnh
if-elsetrực tiếp trong logic nghiệp vụ để quyết định hạ tầng:
// Anti-pattern: Business logic depends on environment check
if (System.getenv("ENV").equals("prod")) {
return new RedisCacheManager(factory);
} else {
return new ConcurrentMapCacheManager();
}
Cơ chế Spring Profiles giải quyết triệt để vấn đề này bằng cách chuyển toàn bộ logic lựa chọn môi trường vào Spring Container. Container kiểm tra profile kích hoạt lúc khởi động và tự động lắp ghép bean phù hợp mà mã nguồn nghiệp vụ không cần hay biết.
Bản chất cơ chế lưu trữ profile trong Spring là gì?
Trong Spring, profile là một chuỗi định danh được quản lý bên trong đối tượng Environment. Khi ứng dụng bắt đầu, container duy trì hai tập hợp nhãn:
- Active profiles: Danh sách các môi trường đang được bật tường minh (ví dụ:
["prod", "monitoring"]). - Default profiles: Nhãn dự phòng được sử dụng khi danh sách active profiles rỗng (mặc định là
["default"]).

Hai hiệu ứng cốt lõi khi một profile được kích hoạt:
- Nạp thêm file cấu hình: Spring Boot tự động bổ sung
application-{profile}.ymlvào chuỗiPropertySource. - Đăng ký bean có chọn lọc: Chỉ những bean khớp với nhãn active mới được đưa vào
beanDefinitionMap. Những bean không khớp sẽ hoàn toàn không tồn tại trong bộ nhớ container.
Có những phương thức kích hoạt profile nào theo thứ tự ưu tiên?
Spring Boot hỗ trợ nhiều cơ chế kích hoạt profile khác nhau với độ ưu tiên tăng dần:
- Khai báo trong
application.yml: Đặt profile mặc định cho môi trường phát triển cục bộ (spring.profiles.active: dev). - Biến môi trường hệ điều hành: Chuẩn phổ biến nhất trong Docker và Kubernetes (
SPRING_PROFILES_ACTIVE=prod). - Tham số JVM System Property: Truyền cờ lúc chạy tiến trình máy ảo (
-Dspring.profiles.active=prod). - Tham số dòng lệnh command line: Ghi đè trực tiếp lúc thực thi (
--spring.profiles.active=prod). - Annotation kiểm thử
@ActiveProfiles: Kích hoạt riêng trong môi trường test context (@ActiveProfiles("test")).
# Kubernetes deployment manifest using environment variable
spec:
containers:
- name: api
env:
- name: SPRING_PROFILES_ACTIVE
value: "prod"
Nhờ cơ chế relax binding, chuỗi SPRING_PROFILES_ACTIVE tự động ánh xạ vào thuộc tính spring.profiles.active của Spring Boot.
Cách áp dụng annotation @Profile trên các tầng ứng dụng?
Annotation @Profile có thể đặt ở ba vị trí linh hoạt:
Đầu tiên là đặt trên phương thức cấu hình @Bean:
@Configuration
public class CacheConfiguration {
@Bean
@Profile("dev")
public CacheManager devCache() {
return new ConcurrentMapCacheManager();
}
@Bean
@Profile("prod")
public CacheManager prodCache(RedisConnectionFactory factory) {
return new RedisCacheManager(factory);
}
}
Thứ hai là gắn trực tiếp trên các lớp thành phần @Component:
@Component
@Profile("prod")
public class CloudWatchMetricsExporter implements MetricsExporter {
// Production CloudWatch implementation
}
@Component
@Profile("dev")
public class ConsoleMetricsExporter implements MetricsExporter {
// Development console implementation
}
Thứ ba là gắn trên toàn bộ lớp cấu hình @Configuration:
@Configuration
@Profile("prod")
public class ProductionInfrastructureConfig {
@Bean public DataSource dataSource() { return createProdDataSource(); }
@Bean public CacheManager cache() { return createProdCache(); }
}
Toàn bộ các bean định nghĩa bên trong ProductionInfrastructureConfig sẽ chỉ được nạp khi profile prod được kích hoạt.
Vì sao Spring đánh giá @Profile ngay lúc đăng ký BeanDefinition?
Annotation @Profile thực chất là một biến thể của @Conditional, được gắn siêu nhãn @Conditional(ProfileCondition.class).
Bộ xử lý ConfigurationClassPostProcessor duyệt qua các bean trong giai đoạn refresh() của container. Tại thời điểm này, đối tượng ProfileCondition kiểm tra môi trường:
- Nếu profile không khớp, Spring không tạo
BeanDefinitioncho lớp đó. - Bean không có định nghĩa trong
beanDefinitionMap, tương đương với việc nó chưa từng tồn tại trong hệ thống.
Việc đánh giá ngay lúc đăng ký giúp phát hiện lỗi thiếu phụ thuộc ngay tại thời điểm khởi động. Nếu một service thông thường phụ thuộc vào một bean chỉ có ở profile prod mà ứng dụng đang chạy ở dev, container sẽ dừng ngay lập tức với lỗi NoSuchBeanDefinitionException. Điều này ngăn ngừa hoàn toàn nguy cơ lỗi phát sinh bất ngờ lúc runtime khi người dùng gửi request.
Điểm khác biệt giữa bean mặc định và bean @Profile("default") là gì?
Hành vi nạp bean phụ thuộc vào cách khai báo nhãn:
| Loại bean | Không kích hoạt profile | Kích hoạt dev | Kích hoạt prod |
|---|---|---|---|
Không có @Profile | Luôn được nạp | Luôn được nạp | Luôn được nạp |
@Profile("default") | Được nạp | Bị bỏ qua | Bị bỏ qua |
@Profile("dev") | Bị bỏ qua | Được nạp | Bị bỏ qua |
Bean không gắn @Profile được coi là phổ quát và luôn tồn tại. Ngược lại, bean đánh dấu @Profile("default") chỉ được tạo khi không có bất kỳ profile nào khác đang hoạt động. Đây là nơi thích hợp để khởi tạo dữ liệu mẫu cho lập trình viên chạy ứng dụng độc lập lúc mới clone source code.
Tóm tắt
- Spring Profiles giúp cô lập cấu hình và bean theo môi trường trên một artifact duy nhất.
- Có 5 phương thức kích hoạt với độ ưu tiên rõ ràng, trong đó biến môi trường là chuẩn mực cho container và Kubernetes.
@Profilehoạt động như một điều kiện@Conditional, lọc bean ngay tại pha đăng kýBeanDefinitiontrước khi tạo instance.- Cơ chế fail-fast tại startup ngăn chặn triệt để lỗi thiếu dependency tiềm ẩn khi triển khai sai môi trường.
- Luôn inject dependency thông qua interface để đảm bảo mọi profile đều có implementation tương thích.
Tự kiểm tra
- Q1Vì sao Spring Container lại đánh giá điều kiện @Profile tại pha đăng ký BeanDefinition thay vì lúc gọi getBean()?
- Q2Phương thức kích hoạt profile nào được ưu tiên cao nhất trong quá trình chạy kiểm thử tự động?
- Q3Một bean được đánh dấu @Profile('default') sẽ được Spring nạp vào container trong điều kiện nào?
- Q4Khi ứng dụng chạy với profile 'dev', điều gì xảy ra nếu một Service thông thường inject một Component chỉ có nhãn @Profile('prod')?
- Q5Annotation @Profile trong Spring Core được hiện thực dựa trên cơ chế nền tảng nào?
Bài tiếp theo: Profile groups, multi-doc YAML & patterns
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.
@ConfigurationProperties vs @Value: Type-Safe Binding
Bài tiếpProfile Groups & Patterns: Quản Trị Cấu Hình Môi Trường
Lộ trình Junior Java Backend Developer
Chặng 14/21 · Spring Boot & autoconfiguration — bài 12/16 của chặng
Thảo luận & góp ý
Hỏi đáp về bài này