В мире Spring Boot почти каждый сталкивался с аннотациями @ConditionalOnProperty, @ConditionalOnBean и их собратьями. Они помогают конфигурировать бины динамически, но стандартные условия это только вершина айсберга. Что если вам нужен гибкий, декларативный, строго-типизированный, который понимает Collection, Map, Enum и т.д.?

Именно для этого я разработал conditionals-spring-boot - маленькую библиотеку для расширенных conditional-аннотаций, полностью интегрируемую с Spring Boot 3 и Java 17.

Почему стандартные условия не всегда работают

В Spring Boot @ConditionalOnProperty позволяет проверять простые свойства, но:

  • Нельзя писать простые сравнения и матчи с помощью аннотаций (@ConditionalOnExpression не в счет).

  • Нельзя строго типизировать значение (в обычном Spring Boot есть только String и boolean).

Что умеет моя библиотека

1. Поддержка многих часто используемых типов:

Ниже можно увидеть несколько примеров использования аннотаций библиотеки:

@ConditionalOnStringProperty

@ConditionalOnStringProperty(
        name = "app.region",
        havingValue = "eu",
        trim = true
)
@ConditionalOnStringProperty(
        name = "app.region",
        havingValue = "EU",
        ignoreCase = true
)
@ConditionalOnStringProperty(
        name = "app.version",
        havingValue = "v\\d+\\.\\d+",
        matchType = StringMatchType.MATCHES
)

@ConditionalOnIntegerProperty

@ConditionalOnIntegerProperty(
        name = "app.threads",
        havingValue = 4
)
@ConditionalOnIntegerProperty(
        name = "app.threads",
        havingValue = 16,
        matchType = ComparableMatchType.LESS_THAN
)

@ConditionalOnFloatProperty

@ConditionalOnFloatProperty(
        name = "app.ratio",
        havingValue = 0.5f
)
@ConditionalOnFloatProperty(
        name = "app.ratio",
        havingValue = 0.7F,
        matchType = ComparableMatchType.GREATER_THAN_OR_EQUAL
)

@ConditionalOnDurationProperty

@ConditionalOnDurationProperty(
        name = "app.timeout",
        havingValue = "5s",
        matchType = ComparableMatchType.GREATER_THAN
)
@ConditionalOnDurationProperty(
        name = "app.timeout",
        havingValue = "10s"
)

@ConditionalOnCharacterProperty

@ConditionalOnCharacterProperty(
        name = "app.letter",
        havingValue = 'A'
)

@ConditionalOnCollectionProperty

@ConditionalOnCollectionProperty(
        name = "app.tags",
        havingValue = {"red", "blue"},
        matchType = CollectionMatchType.CONTAINS_ALL
)
@ConditionalOnCollectionProperty(
        name = "app.tags",
        havingValue = {"yellow", "green"},
        matchType = CollectionMatchType.CONTAINS_ANY
)
@ConditionalOnCollectionProperty(
        name = "app.tags",
        size = 3
)

@ConditionalOnMapProperty

@ConditionalOnMapProperty(
        name = "app.labels",
        havingValue = {                         // массив ключ-значение
                "env", "prod",
                 "region", "eu"
        },
        matchType = MapMatchType.CONTAINS_ALL
)
@ConditionalOnMapProperty(
        name = "app.labels",
        havingValue = {"env", "prod"},  // массив ключ-значение
        matchType = MapMatchType.CONTAINS_ANY
)

@ConditionalOnEnumProperty

@ConditionalOnEnumProperty(
        name = "app.labels",
        havingValue = "info",               // не кейс-сенситив
        enumType = LogLevel.class
)

enum LogLevel {
    TRACE, DEBUG, INFO, WARN, ERROR
}

2. Repeatable и контейнерные аннотации

Практически все аннотации можно повторять (кроме @ConditionalOnOs и @ConditionalOnPortAvailable).

3. Унифицированная спецификация через PropertySpec

4. Проверку ОС:

@ConditionalOnOs("linux")

5. Проверку занятости порта:

@ConditionalOnPortAvailable(8080)

Внутри библиотеки все property-based условия описаны через единый контракт PropertySpec + PropertySpecMatcher. Это минимизирует копипасту и упрощает тестирование.

Кому это будет полезно

  • Разработчикам библиотек и модулей для Spring Boot.

  • Командам, которые пишут DSL на основе configuration properties.

  • Тем, кто сталкивался с boilerplate и ограничениями стандартных @Conditional аннотаций.

Заключение

Если вам интересно попробовать, документация и примеры доступны на GitHub. (и поставьте звёздочку :D)