Перейти к основному содержимому

Spring условные аннотации

· 4 мин. чтения

1. Введение

В этом уроке мы рассмотрим аннотацию @Conditional . Он используется, чтобы указать, подходит ли данный компонент для регистрации на основе определенного условия.

Мы узнаем, как использовать предопределенные условные аннотации, комбинировать их с различными условиями, а также создавать собственные аннотации на основе условий.

2. Объявление условий

Прежде чем мы перейдем к реализации, давайте сначала посмотрим, в каких ситуациях мы могли бы использовать условные аннотации.

Наиболее распространенным использованием было бы включение или исключение всего класса конфигурации :

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

Или только один компонент:

@Configuration
class DevEnvLoggingConfiguration {

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

Поступая таким образом, мы можем основывать поведение нашего приложения на заданных условиях. Например, тип среды или конкретные потребности наших клиентов. В приведенном выше примере мы инициализируем дополнительные службы ведения журналов только для среды разработки.

Другой способ сделать компонент условным — поместить условие непосредственно в класс компонента:

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

Мы можем применить приведенный выше пример к любому компоненту, объявленному с помощью аннотаций @Component , @Service , @Repository или @Controller .

3. Предопределенные условные аннотации

Spring поставляется с набором предопределенных условных аннотаций. Давайте пройдемся по некоторым из самых популярных.

Во-первых, давайте посмотрим, как мы можем создать компонент на основе значения свойства конфигурации :

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

Первый атрибут, значение, сообщает нам, какое свойство конфигурации мы будем рассматривать. Второй, haveValue, определяет значение, необходимое для этого условия. И, наконец, атрибут matchIfMissing сообщает Spring, следует ли выполнять условие, если параметр отсутствует.

Точно так же мы можем основывать условие на выражении :

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

Теперь Spring будет создавать LoggingService только в том случае, если для свойства конфигурации logging.enabled установлено значение true, а для logging.level установлено значение DEBUG.

Еще одно условие, которое мы можем применить, — это проверить, был ли создан данный bean-компонент:

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

Или данный класс существует в пути к классам:

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

Мы можем добиться противоположного поведения, применив аннотации @ConditionalOnMissingBean или @ConditionalOnMissingClass .

Кроме того, мы можем зависеть наши компоненты от данной версии Java :

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

В приведенном выше примере LoggingService будет создан только в том случае, если среда выполнения — Java 8.

Наконец, мы можем использовать аннотацию @ConditionalOnWarDeployment , чтобы включить bean-компонент только в военной упаковке:

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

Обратите внимание, что для приложений со встроенными серверами это условие вернет false.

4. Определение пользовательских условий

Spring позволяет нам настраивать поведение аннотации @Conditional , создавая собственные шаблоны условий . Чтобы создать его, нам просто нужно реализовать интерфейс Condition :

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

Метод match сообщает Spring , выполнено условие или нет. У него есть два аргумента, которые дают нам соответственно информацию о контексте, в котором bean-компонент будет инициализирован, и метаданные используемой аннотации @Conditional .

Как мы видим, в нашем примере мы просто проверяем, является ли версия Java 8.

После этого мы должны разместить наше новое условие в качестве атрибута в аннотации @Conditional :

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

Таким образом, Java8DependentService будет создан только там, где выполняется условие из класса Java8Condition .

5. Сочетание условий

Для более сложных решений мы можем сгруппировать условные аннотации с логическими операторами ИЛИ или И.

Чтобы применить оператор ИЛИ, нам нужно создать пользовательское условие, расширяющее класс AnyNestedCondition . Внутри него нам нужно создать пустой статический класс для каждого условия и аннотировать его соответствующей реализацией @Conditional .

Например, давайте создадим условие, требующее либо Java 8, либо Java 9:

class Java8OrJava9 extends AnyNestedCondition {

Java8OrJava9() {
super(ConfigurationPhase.REGISTER_BEAN);
}

@Conditional(Java8Condition.class)
static class Java8 { }

@Conditional(Java9Condition.class)
static class Java9 { }

}

Оператор AND, с другой стороны, намного проще. Мы можем просто сгруппировать условия:

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

В приведенном выше примере служба LoggingService будет создана только в том случае, если совпадают условия IsWindowsCondition и Java8Condition .

6. Резюме

В этой статье мы узнали, как использовать и создавать условные аннотации. Как всегда, весь исходный код доступен на GitHub .