Автоконфигурация, вероятно, является одной из наиболее важных причин, по которой вы решили использовать такие среды, как Spring Boot. Благодаря этой функции обычно достаточно просто включить дополнительную библиотеку и переопределить некоторые свойства конфигурации, чтобы успешно использовать ее в вашем приложении.
Spring предоставляет простой способ определения автоконфигурации с использованием стандартных @Configurationклассов.
Автоконфигурация является одним из аспектов, связанных с конфигурацией Spring Boot. Я уже описывал наиболее интересные функции внешней конфигурации в своей статье «Внешняя конфигурация Magic Around Spring Boot» .
Вам также может понравиться:
Как работает Spring Auto-Configuration
пример
Исходный код с примером приложения, как обычно, доступен на GitHub. Вот адрес примера репозитория: https://github.com/piomin/springboot-configuration-playground.git .
Тестирование автоконфигурации
Давайте начнем необычным способом — с тестирования. Spring Boot предоставляет очень удобный механизм для автоматической настройки конфигурации. Нам просто нужно создать экземпляр ApplicationContextRunnerв нашем тесте JUnit. С помощью ApplicationContextRunnerмы можем легко манипулировать путем к классам, включать некоторые файлы свойств в контекст Spring и, наконец, объявлять список классов входной конфигурации. Благодаря этому нам даже не нужно аннотировать наш класс конфигурации @Configuration, чтобы иметь возможность его протестировать.
Джава
1
public class MyConfiguration {
2
4
(MyBean2.class)
5
public MyBean1 myBean1() {
6
return new MyBean1();
7
}
8
9
}
myBean1Зависит от MyBean2класса , так как он помечается @ConditionalOnClass. Давайте посмотрим на список всех классов, определенных для нашей текущей демонстрации.

Как вы видите на картинке выше, MyBean2класс доступен на classpath. Поэтому нам нужно удалить его во время теста, чтобы проверить состояние и получить ожидаемое NoSuchBeanDefinitionExceptionисключение во время теста. Мы можем использовать FilteredClassLoaderкласс для этого.
Джава
xxxxxxxxxx
1
(expected = NoSuchBeanDefinitionException.class)
2
public void testMyBean1() {
3
final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
4
contextRunner.withUserConfiguration(MyConfiguration.class)
5
withClassLoader(new FilteredClassLoader(MyBean2.class))
6
.run(context -> {
7
MyBean1 myBean1 = context.getBean(MyBean1.class);
8
Assert.assertEquals("I'm MyBean1", myBean1.me());
9
});
10
}
@ConditionalOnProperty
Это @ConditionalOnPropertyдовольно интересная аннотация. Это позволяет включать конфигурацию только в том случае, если свойство среды существует, не существует или имеет конкретное значение. Давайте предположим, что у нас есть другой компонент, myBean2определенный внутри нашего класса конфигурации.
Джава
xxxxxxxxxx
1
2
("myBean2.enabled")
3
public MyBean2 myBean2() {
4
return new MyBean2();
5
}
Мы добавим свойство myBean2.enabledво ApplicationContextRunnerвремя теста JUnit. Результат может быть немного удивительным. myBean2Боб не доступен в контексте (исключение NoSuchBeanDefinitionExceptionпроисходит). Зачем? Документация Spring Boot поставляется с ответом: По умолчанию любое свойство, которое существует и не равно, falseсопоставляется. Поскольку мы присваиваем значение false нашему свойству ( withPropertyValues("myBean2.enabled=false")), результат теста становится понятным.
Джава
xxxxxxxxxx
1
(expected = NoSuchBeanDefinitionException.class)
2
public void testMyBean2Negative() {
3
final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
4
contextRunner
5
.withPropertyValues("myBean2.enabled=false")
6
.withUserConfiguration(MyConfiguration.class)
7
.run(context -> {
8
MyBean2 myBean2 = context.getBean(MyBean2.class);
9
Assert.assertEquals("I'm MyBean2", myBean2.me());
10
});
11
}
Допустимо любое значение, отличное от false, включая пустое значение. Вот пример положительного теста.

Чтобы создать условный компонент, зависящий от значения свойства, нам нужно использовать поле havingValue. Предполагая, что у нас есть следующее определение bean-компонента, которое зависит от свойства myBean5.disabled, нам нужно переопределить правило, согласно которому значение falseприводит к недоступности myBean5bean-компонента.
Джава
xxxxxxxxxx
1
2
(value = "myBean5.disabled", havingValue = "false")
3
public MyBean5 myBean5() {
4
return new MyBean5();
5
}
6
Несколько условий
Мы можем смешивать разные условные аннотации в одном определении бина. Мы не можем дублировать одну и ту же аннотацию, но можно добавить несколько классов, компонентов или свойств в одну условную аннотацию. Каждое из этих условий логически объединяется с использованием AND . Следующий боб myBean4зависит от multipleBeans.enabledсобственности, и myBean1, myBean2бобы.
xxxxxxxxxx
1
2
("multipleBeans.enabled")
3
({MyBean1.class, MyBean2.class})
4
public MyBean4 myBean4() {
5
return new MyBean4();
6
}
Следующий тест проверяет случай, когда только myBean2недоступно. Это приводит к NoSuchBeanDefinitionExceptionисключению.
Джава
xxxxxxxxxx
1
(expected = NoSuchBeanDefinitionException.class)
2
public void testMyBean4Negative() {
3
final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
4
contextRunner
5
.withUserConfiguration(MyConfiguration.class)
6
.withPropertyValues("multipleBeans.enabled")
7
.run(context -> {
8
MyBean4 myBean4 = context.getBean(MyBean4.class);
9
Assert.assertEquals("I'm MyBean4", myBean4.me());
10
});
11
}
Поскольку myBean2это зависит от myBean2.enabledсвойства, нам нужно включить его в контекст во время теста, чтобы проверить положительный сценарий. В следующем тесте JUnit myBean4выполняются все три условия для , что приводит к доступности компонента.

Теперь давайте рассмотрим ситуацию, в которой мы хотели бы иметь те же условия, определенные для нашего компонента, но логически объединенные с использованием OR . У нас нет предопределенных аннотаций для таких случаев. Итак, нам нужно создать класс, который расширяет AnyNestedConditionкласс Spring и определяет все три условия, как показано ниже.
Джава
xxxxxxxxxx
1
public class MyBeansOrPropertyCondition extends AnyNestedCondition {
2
public MyBeansOrPropertyCondition() {
4
super(ConfigurationPhase.REGISTER_BEAN);
5
}
6
(MyBean1.class)
8
static class MyBean1ExistsCondition {
9
}
11
(MyBean2.class)
13
static class MyBean2ExistsCondition {
14
}
16
("multipleBeans.enabled")
18
static class MultipleBeansPropertyExists {
19
}
21
}
Класс, определенный выше, должен использоваться как условие для нашего нового компонента. Вот myBean6определение:
Джава
xxxxxxxxxx
1
2
(MyBeansOrPropertyCondition.class)
3
public MyBean6 myBean6() {
4
return new MyBean6();
5
}
В следующем тесте выполняется только условие с myBean1. Поскольку все наши три условия логически связаны, использование OR, myBean6доступно в контексте. Также стоит упомянуть, что Spring Boot предоставляет класс NoneNestedConditionдля построения негативных условий.

Порядок загрузки автоконфигурации
Мы можем определить несколько @Configurationв нашем приложении. При наличии нескольких бобов конфигурации, мы можем легко контролировать порядок загрузки с помощью аннотаций @AutoConfigureAfter, @AutoConfigureBeforeили @AutoConfigureOrder. В тесте с ApplicationContextRunner, порядок загрузки просто представлен аргументами порядка, используемыми в withUserConfigurationметоде.
Джава
xxxxxxxxxx
1
2
public void testOrder() {
3
final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
4
contextRunner
5
.withUserConfiguration(MyConfiguration.class, MyConfigurationOverride.class)
6
.withPropertyValues("myBean2.enabled")
7
.run(context -> {
8
MyBean2 myBean2 = context.getBean(MyBean2.class);
9
Assert.assertEquals("I'm MyBean2 overridden", myBean2.me());
10
});
11
}
Давайте рассмотрим ситуацию, когда у нас есть два объявления одного и того же компонента в двух классах конфигурации. Вот наш боб:
Джава
xxxxxxxxxx
1
public class MyBean2 {
2
private String me = "I'm MyBean2";
4
public String me() {
6
return me;
7
}
8
void setMe(String me) {
10
this.me = me;
11
}
12
}
Мы определяем второй класс конфигурации Spring, который переопределяет myBean2объявление.
Джава
xxxxxxxxxx
1
public class MyConfigurationOverride {
2
4
public MyBean2 myBean2() {
5
MyBean2 b = new MyBean2();
6
b.setMe("I'm MyBean2 overridden");
7
return b;
8
}
9
10
}
Каков будет результат операции, видимой выше? Он переопределяет существующее определение компонента, как показано ниже.

Дополнительные условные аннотации
Есть некоторые дополнительные условные аннотации , как @ConditionalOnExpression, @ConditionalOnSingleCandidateили @ConditionalOnWebApplication. Для получения более подробной информации вы можете проверить документацию Spring Boot. Существует также @ConditionalOnJava, который позволяет вам определить версию Java, в которой должен быть доступен определенный бин. Вот определение, где MyBean3регистрируется, только если версия Java новее, чем 8.
Джава
xxxxxxxxxx
1
2
(range = ConditionalOnJava.Range.EQUAL_OR_NEWER, value = JavaVersion.NINE)
3
public MyBean3 myBean3() {
4
return new MyBean3();
5
}
Вот тестовый запуск под Java 8.

Резюме
В этой статье я продемонстрировал наиболее интересные возможности автоконфигурации Spring Boot. Собрать автоконфигурацию в Spring Boot довольно просто, а может, даже весело? Вот результат запуска наших тестов.

Дальнейшее чтение
Как работает Spring Auto-Configuration
7 вещей, которые нужно знать перед началом работы с Spring Boot
Учебник. Реактивная загрузка Spring, часть 5 - Автоматическая настройка для общих компонентов