1.Giới thiệu

Trong hướng dẫn này, chúng ta sẽ xem xét chú thích @Conditional . Nó được sử dụng để cho biết liệu một thành phần nhất định có đủ điều kiện để đăng ký hay không dựa trên một điều kiện đã xác định.

Chúng ta sẽ tìm hiểu cách sử dụng chú thích Conditional được xác định trước, kết hợp chúng với các điều kiện khác nhau và tạo chú thích dựa trên điều kiện, tùy chỉnh của riêng chúng ta.

2. Khai báo @Conditional

Trước khi chuyển sang triển khai, hãy xem những tình huống nào chúng ta có thể sử dụng @Conditional.

Cách sử dụng phổ biến nhất là bao gồm hoặc loại trừ toàn bộ lớp cấu hình:

@Configuration
@Conditional(IsDevEnvCondition.class)
class DevEnvLoggingConfiguration {
    // ...
}

Hoặc sử dụng với Bean:

@Configuration
class DevEnvLoggingConfiguration {
    
    @Bean
    @Conditional(IsDevEnvCondition.class)
    LoggingService loggingService() {
        return new LoggingService();
    }
}

Bằng cách đó, chúng ta có thể căn cứ vào hành vi của ứng dụng của mình dựa trên các điều kiện nhất định, chẳng hạn như loại môi trường hoặc nhu cầu cụ thể của khách hàng. Trong ví dụ trên, chúng ta đã khởi tạo logging services chỉ dành cho môi trường phát triển.

Một cách khác để tạo thành phần có điều kiện là đặt @Conditional trực tiếp lên class:

@Service
@Conditional(IsDevEnvCondition.class)
class LoggingService {
    // ...
}

Chúng ta có thể áp dụng ví dụ trên cho bất kỳ Bean nào được khai báo bằng các chú thích @Component, @Service, @Repository hoặc @Controller.

3. Một số Conditional Annotations phổ biến

@ConditionalOnProperty được sử dụng khi bạn muốn quyết định việc tạo bean dựa trên giá trị của một thuộc tính cấu hình (property).

@Service
@ConditionalOnProperty(
  value="logging.enabled", 
  havingValue = "true", 
  matchIfMissing = true)
class LoggingService {
    // ...
}

Ở đây, thuộc tính value cho chúng ta biết thuộc tính cấu hình nào chúng ta sẽ xem xét. Thuộc tính havingValue sẽ xác định điều kiện bắt buộc cho điều kiện này. Nếu giá trị bằng true thì lớp cấu hình được kích hoạt và các bean trong đó được sử dụng. Còn thuộc tính matchIfMissing = true là nếu thuộc tính không tồn tại thì lớp cấu hình vẫn sẽ được kích hoạt và các bean trong đó được sử dụng nếu bằng false thì ngược lại.

@ConditionalOnExpression được sử dụng khi bạn muốn đặt nhiều điều kiện và thỏa mãn điều kiện này bằng một biểu thức.

@Service
@ConditionalOnExpression(
  "${logging.enabled:true} and '${logging.level}'.equals('DEBUG')"
)
class LoggingService {
    // ...
}

Bây giờ Spring sẽ chỉ tạo LoggingService khi cả thuộc tính cấu hình log.enabled được đặt thành true và log.level được đặt thành DEBUG.

Một điều kiện khác mà chúng ta có thể áp dụng là kiểm tra xem Bean đã cho có được tạo hay không:

@Service
@ConditionalOnBean(CustomLoggingConfiguration.class)
class LoggingService {
    // ...
}

Hoặc một class tồn tại trong classpath:

@Service
@ConditionalOnClass(CustomLogger.class)
class LoggingService {
    // ...
}

Chúng ta có thể đạt được hành vi ngược lại bằng cách áp dụng các chú thích @ConditionalOnMissingBean hoặc @ConditionalOnMissingClass .

Ngoài ra, chúng ta có thể phụ thuộc các thành phần của mình vào một phiên bản Java nhất định :

@Service
@ConditionalOnJava(JavaVersion.EIGHT)
class LoggingService {
    // ...
}

Trong ví dụ trên, LoggingService sẽ chỉ được tạo khi môi trường thời gian chạy là Java 8.

Cuối cùng, chúng ta có thể sử dụng chú thích @ConditionalOnWarDeployment để chỉ kích hoạt Bean trong war package:

@Configuration
@ConditionalOnWarDeployment
class AdditionalWebConfiguration {
    // ...
}

Lưu ý rằng đối với các ứng dụng có máy chủ nhúng, điều kiện này sẽ trả về sai.

4. Tùy chỉnh conditional

Spring cho phép chúng ta tùy chỉnh hành vi của chú thích @Conditional bằng cách tạo các mẫu điều kiện tùy chỉnh . Để tạo một cái, chúng ta chỉ cần triển khai Condition interface:

class Java8Condition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return JavaVersion.getJavaVersion().equals(JavaVersion.EIGHT);
    }
}

Phương thức match cho Spring biết điều kiện đã đạt hay chưa. Nó có hai đối số cung cấp cho chúng ta thông tin về context nơi Bean sẽ khởi tạo và metadata của chú thích @Conditional đã sử dụng.

Như chúng ta có thể thấy trong ví dụ của mình, chúng ta chỉ kiểm tra xem phiên bản Java có phải là 8 hay không.

Sau đó, chúng ta nên đặt điều kiện mới làm thuộc tính trong chú thích @Conditional:

@Service
@Conditional(Java8Condition.class)
public class Java8DependedService {
    // ...
}

Bằng cách này, Java8DependentService sẽ chỉ được tạo khi điều kiện từ lớp Java8Condition khớp với nhau.

5. Kết hợp conditional

Đối với các giải pháp phức tạp hơn, chúng ta có thể nhóm các chú thích coditional bằng toán tử logic OR hoặc AND.

Để áp dụng toán tử OR, chúng ta cần tạo một điều kiện tùy chỉnh mở rộng lớp AnyNestedCondition. Bên trong nó, chúng ta cần tạo một static class trống cho từng điều kiện và chú thích nó bằng cách triển khai @Conditional thích hợp.

Ví dụ: hãy tạo một điều kiện yêu cầu Java 8 hoặc Java 9:

class Java8OrJava9 extends AnyNestedCondition {
    
    Java8OrJava9() {
        super(ConfigurationPhase.REGISTER_BEAN);
    }
    
    @Conditional(Java8Condition.class)
    static class Java8 { }
    
    @Conditional(Java9Condition.class)
    static class Java9 { }
    
}

Mặt khác, toán tử AND đơn giản hơn nhiều. Chúng ta có thể chỉ cần nhóm các điều kiện:

@Service
@Conditional({IsWindowsCondition.class, Java8Condition.class})
@ConditionalOnJava(JavaVersion.EIGHT)
public class LoggingService {
    // ...
}

Trong ví dụ trên, LoggingService sẽ chỉ được tạo khi cả IsWindowsCondition và Java8Condition đều khớp.

6. Kết luận

Trong bài viết này, chúng ta đã tìm hiểu cách sử dụng và tạo conditional annotations
Link bài viết tham khảo