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

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.

Nhớ lại — PropertySource Ordering
Nhớ lại — @ConfigurationProperties

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:

  1. Đó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.
  2. Kiểm tra môi trường bằng câu lệnh điều kiện: Viết lệnh if-else trực tiếp trong logic nghiệp vụ để quyết định hạ tầng:
Java
// 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"]).

Cùng một JAR khởi động hai lần với profile dev và prod; ProfileCondition trả lời khác nhau nên beanDefinitionMap mỗi bên chỉ chứa một CacheManager, bean không khớp hoàn toàn không có định nghĩa

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}.yml vào chuỗi PropertySource.
  • Đă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:

  1. 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).
  2. 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).
  3. Tham số JVM System Property: Truyền cờ lúc chạy tiến trình máy ảo (-Dspring.profiles.active=prod).
  4. Tham số dòng lệnh command line: Ghi đè trực tiếp lúc thực thi (--spring.profiles.active=prod).
  5. Annotation kiểm thử @ActiveProfiles: Kích hoạt riêng trong môi trường test context (@ActiveProfiles("test")).
YAML
# 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:

Java
@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:

Java
@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:

Java
@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 BeanDefinition cho 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 beanKhông kích hoạt profileKích hoạt devKích hoạt prod
Không có @ProfileLuôn được nạpLuôn được nạpLuôn được nạp
@Profile("default")Được nạpBị bỏ quaBị bỏ qua
@Profile("dev")Bị bỏ quaĐược nạpBị 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.
  • @Profile hoạt động như một điều kiện @Conditional, lọc bean ngay tại pha đăng ký BeanDefinition trướ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

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Vì 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()?
  2. Q2
    Phươ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?
  3. Q3
    Một bean được đánh dấu @Profile('default') sẽ được Spring nạp vào container trong điều kiện nào?
  4. Q4
    Khi ứ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')?
  5. Q5
    Annotation @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.

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

Bài tiếp theo

Profile Groups & Patterns: Quản Trị Cấu Hình Môi Trường