Автоконфигурация, вероятно, является одной из наиболее важных причин, по которой вы решили использовать такие среды, как 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
приводит к недоступности myBean5
bean-компонента.
Джава
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 - Автоматическая настройка для общих компонентов