Как работает аннотация @Conditional в Spring?

Ответ

@Conditional — это базовая аннотация Spring для условной регистрации бинов в контексте приложения. Условие определяется реализацией интерфейса Condition.

Принцип работы: Spring проверяет условие, заданное в @Conditional, и регистрирует бин только если оно выполняется (matches() возвращает true).

Пример с пользовательским условием:

@Configuration
public class FeatureConfig {
    // Бин зарегистрируется, только если выполнится MyFeatureCondition
    @Bean
    @Conditional(MyFeatureCondition.class)
    public MyService myService() {
        return new MyService();
    }
}

public class MyFeatureCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // Проверяем наличие системного свойства или переменной окружения
        String featureFlag = context.getEnvironment().getProperty("my.feature.enabled");
        return "true".equalsIgnoreCase(featureFlag);
    }
}

Встроенные производные аннотации (более удобные): Spring Boot предоставляет множество готовых аннотаций на основе @Conditional:

  • @ConditionalOnProperty: Зависит от значения свойства.
    @Bean
    @ConditionalOnProperty(name = "cache.provider", havingValue = "redis")
    public CacheManager redisCacheManager() { ... }
  • @ConditionalOnClass / @ConditionalOnMissingClass: Зависит от наличия/отсутствия класса в classpath.
    @Bean
    @ConditionalOnClass(name = "com.example.SomeClient")
    public SomeService someService() { ... }
  • @ConditionalOnBean / @ConditionalOnMissingBean: Зависит от наличия/отсутствия другого бина в контексте.
    @Bean
    @ConditionalOnMissingBean(DataSource.class)
    public DataSource inMemoryDataSource() { ... }
  • @ConditionalOnWebApplication / @ConditionalOnNotWebApplication: Зависит от типа приложения.

Основное применение: Создание автоконфигураций, включение фич по флагам, выбор реализации в зависимости от окружения.

Ответ 18+ 🔞

А, слушай, вот эта аннотация @Conditional — это же, блядь, просто песня, а не инструмент! Представь себе: ты такой сидишь, настраиваешь свой Spring-контекст, и тут тебе надо, чтобы бин появлялся не просто так, а только если какое-то условие выполнилось. Ну, например, если в настройках флажок my.feature.enabled стоит true, или если в classpath какая-то библиотека лежит. Вот тут-то она, родимая, и выручает.

По сути, это такая прищепка на контекст, которая говорит: «Стой, блядь, не регистрируй этот бин, пока не проверишь вот эту хуйню». А проверяет она её через интерфейс Condition, где ты сам пишешь логику в методе matches(). Вернул true — бин живой, вернул false — бин отправился в утиль, даже не родившись.

Вот смотри, как это выглядит в коде, чтоб ты проникся:

@Configuration
public class FeatureConfig {
    // Этот бин Spring создаст только если MyFeatureCondition скажет "да, блядь, можно"
    @Bean
    @Conditional(MyFeatureCondition.class)
    public MyService myService() {
        return new MyService();
    }
}

public class MyFeatureCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // Лезу в настройки, ищу свойство
        String featureFlag = context.getEnvironment().getProperty("my.feature.enabled");
        // Если там "true", то всё, условие выполнено, бин будет
        return "true".equalsIgnoreCase(featureFlag);
    }
}

Но это, понимаешь, ручное управление, как на тракторе. А Spring Boot, он же для ленивых, как я. Он посмотрел на это и говорит: «Да зачем вам каждый раз свою хуйню писать?». И наделал кучу готовых, обёрнутых аннотаций. Это просто огонь, ёпта!

  • @ConditionalOnProperty: Самый частый гость. Бин только если в application.yml или в свойствах нужная запись есть.
    @Bean
    @ConditionalOnProperty(name = "cache.provider", havingValue = "redis") // Если cache.provider=redis, то окей
    public CacheManager redisCacheManager() { ... }
  • @ConditionalOnClass / @ConditionalOnMissingClass: Тут вообще магия. Spring смотрит в classpath. «О, а библиотека com.example.SomeClient есть? Нет? Ну тогда и бин твой, сука, не нужен».
    @Bean
    @ConditionalOnClass(name = "com.example.SomeClient")
    public SomeService someService() { ... }
  • @ConditionalOnBean / @ConditionalOnMissingBean: А это уже интриги внутри самого контекста. «Так-так, а DataSource у нас уже есть? Есть? Ну тогда свой inMemoryDataSource можешь, блядь, засунуть обратно, он не нужен».
    @Bean
    @ConditionalOnMissingBean(DataSource.class) // Создам бин, только если ещё нет ни одного DataSource
    public DataSource inMemoryDataSource() { ... }
  • @ConditionalOnWebApplication / @ConditionalOnNotWebApplication: Ну это совсем просто. Веб-приложение у нас или консольное? От этого зависит.

Зачем это всё, спросишь? Да овердохуища применений! Автоконфигурации в Spring Boot на этом, блядь, построены. Включение экспериментальных фич по флагам, выбор реализации под разные окружения (dev, prod), чтобы не тащить в продакшен отладочные сервисы. В общем, вещь архиполезная, хоть и выглядит как магия. Главное — не переборщить, а то контекст превратится в головоломку, где ни один бин не знает, выживет ли он после старта.