Spec-Zone.ru › Spring Boot

Основные возможности

Этот раздел углубляется в детали Spring Boot. Здесь вы можете узнать о ключевых функциях, которые вы можете использовать и настраивать. Если вы ещё этого не сделали, вам следует прочитать разделы "Начало работы" и "Разработка с Spring Boot", чтобы иметь хорошее понимание основ.

1. SpringApplication

Класс SpringApplication предоставляет удобный способ запустить Spring-приложение, которое стартует из метода main(). Во многих ситуациях вы можете использовать статический метод SpringApplication.run, как показано в следующем примере:

Java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }

}
Kotlin
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication


@SpringBootApplication
class MyApplication

fun main(args: Array<String>) {
    runApplication<MyApplication>(*args)
}

При запуске приложения вы должны увидеть вывод, похожий на следующий:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.1.3)

2023-08-24T09:33:22.083Z  INFO 37822 --- [           main] o.s.b.d.f.logexample.MyApplication       : Starting MyApplication using Java 17.0.8 with PID 37822 (/opt/apps/myapp.jar started by myuser in /opt/apps/)
2023-08-24T09:33:22.088Z  INFO 37822 --- [           main] o.s.b.d.f.logexample.MyApplication       : No active profile set, falling back to 1 default profile: "default"
2023-08-24T09:33:23.339Z  INFO 37822 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2023-08-24T09:33:23.354Z  INFO 37822 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-08-24T09:33:23.355Z  INFO 37822 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.12]
2023-08-24T09:33:23.485Z  INFO 37822 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-08-24T09:33:23.488Z  INFO 37822 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1308 ms
2023-08-24T09:33:23.989Z  INFO 37822 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2023-08-24T09:33:24.000Z  INFO 37822 --- [           main] o.s.b.d.f.logexample.MyApplication       : Started MyApplication in 2.424 seconds (process running for 2.804)

По умолчанию отображаются сообщения журнала INFO, включая некоторые важные детали запуска, такие как пользователь, который запустил приложение. Если вам нужен уровень журнала, отличный от INFO, вы можете его установить, как описано в Уровни журналов. Версия приложения определяется с помощью версии реализации из пакета основного класса приложения. Ведение журнала информации о запуске можно отключить, установив spring.main.log-startup-info в значение false. Это также отключит ведение журнала активных профилей приложения.

Чтобы добавить дополнительные журналы во время запуска, вы можете переопределить logStartupInfo(boolean) в подклассе SpringApplication.

1.1. Ошибка запуска

Если ваше приложение не может запуститься, зарегистрированные FailureAnalyzers получают возможность предоставить специальное сообщение об ошибке и конкретное действие для устранения проблемы. Например, если вы запускаете веб-приложение на порту 8080, а этот порт уже используется, вы увидите сообщение, подобное следующему:

***************************
APPLICATION FAILED TO START
***************************

Description:

Embedded servlet container failed to start. Port 8080 was already in use.

Action:

Identify and stop the process that is listening on port 8080 or configure this application to listen on another port.
Spring Boot предоставляет множество реализаций FailureAnalyzer, и вы можете добавить свои собственные.

Если ни один из анализаторов сбоев не может обработать исключение, вы все равно можете отобразить полный отчет об условиях, чтобы лучше понять, что пошло не так. Для этого необходимо включить свойство debug или включить ведение журнала DEBUG для org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener.

Например, если вы запускаете приложение с помощью java -jar, вы можете включить свойство debug следующим образом:

$ java -jar myproject-0.0.1-SNAPSHOT.jar --debug

1.2. Ленивая инициализация

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

Недостатком ленивой инициализации является то, что она может отложить обнаружение проблемы с приложением. Если бину с неправильной настройкой будет проведена ленивая инициализация, ошибка больше не произойдет во время запуска, и проблема проявится только при инициализации бина. Также необходимо позаботиться о том, чтобы у JVM было достаточно памяти для размещения всех бинов приложения, а не только тех, которые инициализируются во время запуска. По этим причинам ленивая инициализация по умолчанию не включена, и рекомендуется произвести настройку размера кучи JVM перед включением ленивой инициализации.

Ленивую инициализацию можно включить программно, используя метод lazyInitialization для SpringApplicationBuilder или метод setLazyInitialization для SpringApplication. Кроме того, ее можно включить, используя свойство spring.main.lazy-initialization, как показано в следующем примере:

Свойства
spring.main.lazy-initialization=true
Yaml
spring:
  main:
    lazy-initialization: true
Если вы хотите отключить ленивую инициализацию для определенных бинов, используя ленивую инициализацию для остальной части приложения, вы можете явно установить их атрибут lazy в false, используя аннотацию @Lazy(false).

1.3. Настройка баннера

Баннер, который печатается при запуске, может быть изменён путем добавления файла banner.txt в ваш класспатический путь или установкой свойства spring.banner.location для указания местоположения такого файла. Если файл имеет кодировку, отличную от UTF-8, вы можете установить spring.banner.charset.

Внутри вашего файла banner.txt вы можете использовать любой ключ, доступный в Environment, а также следующие плейсхолдеры:

Таблица 1. Переменные баннера
Переменная Описание

${application.version}

Номер версии вашего приложения, как объявлен в MANIFEST.MF. Например, Implementation-Version: 1.0 печатается как 1.0.

${application.formatted-version}

Номер версии вашего приложения, как объявлен в MANIFEST.MF и отформатирован для отображения (в скобках и с префиксом v). Например (v1.0).

${spring-boot.version}

Версия Spring Boot, которую вы используете. Например 3.1.3.

${spring-boot.formatted-version}

Версия Spring Boot, которую вы используете, отформатированная для отображения (в скобках и с префиксом v). Например (v3.1.3).

${Ansi.NAME} (или ${AnsiColor.NAME}, ${AnsiBackground.NAME}, ${AnsiStyle.NAME})

Где NAME — имя кода ANSI. Подробности см. в AnsiPropertySource.

${application.title}

Название вашего приложения, как объявлено в MANIFEST.MF. Например Implementation-Title: MyApp печатается как MyApp.

Метод SpringApplication.setBanner(…​) может быть использован, если вам нужно сгенерировать баннер программно. Используйте интерфейс org.springframework.boot.Banner и реализуйте свой собственный метод printBanner().

Вы также можете использовать свойство spring.main.banner-mode, чтобы определить, должен ли баннер печататься при запуске (console), отправляться в настроенный логгер (log) или вообще не отображаться (off).

Напечатанный баннер регистрируется как одиночный бин под следующим именем: springBootBanner.

Свойства ${application.version} и ${application.formatted-version} доступны только при использовании запуске Spring Boot. Значения не будут разрешены, если вы запускаете распакованный jar и запускаете его с помощью java -cp <classpath> <mainclass>.

Вот почему мы рекомендуем всегда запускать распакованные jar-файлы с помощью java org.springframework.boot.loader.JarLauncher. Это позволит инициализировать переменные баннера application.* перед построением classpath и запуском приложения.

1.4. Настройка SpringApplication

Если значения по умолчанию SpringApplication не подходят, вы можете создать локальный экземпляр и настроить его. Например, чтобы отключить баннер, вы можете написать:

Java
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(MyApplication.class);
        application.setBannerMode(Banner.Mode.OFF);
        application.run(args);
    }

}
Kotlin
import org.springframework.boot.Banner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class MyApplication

fun main(args: Array<String>) {
    runApplication<MyApplication>(*args) {
        setBannerMode(Banner.Mode.OFF)
    }
}
Аргументы конструктора, передаваемые в SpringApplication, являются источниками конфигурации для Spring-бинов. В большинстве случаев это ссылки на классы @Configuration, но это также могут быть прямые ссылки на классы @Component.

Также возможно настроить SpringApplication, используя файл application.properties. Подробности см. в разделе Внешняя конфигурация.

Полный список параметров конфигурации см. в SpringApplication Javadoc.

1.5. API гибкого создания

Если вам нужно создать иерархию (несколько контекстов с родительско-дочерними отношениями) или вы предпочитаете использовать API гибкого создания, вы можете использовать SpringApplicationBuilder.

SpringApplicationBuilder позволяет объединить несколько вызовов методов и включает методы parent и child, которые позволяют создавать иерархию, как показано в следующем примере:

Java
new SpringApplicationBuilder().sources(Parent.class)
    .child(Application.class)
    .bannerMode(Banner.Mode.OFF)
    .run(args);
Kotlin
SpringApplicationBuilder()
    .sources(Parent::class.java)
    .child(Application::class.java)
    .bannerMode(Banner.Mode.OFF)
    .run(*args)
Существуют некоторые ограничения при создании иерархии ApplicationContext. Например, веб-компоненты обязательно должны находиться в дочернем контексте, и один и тот же Environment используется для родительского и дочернего контекстов. Для получения полной информации см. SpringApplicationBuilder Javadoc.

1.6. Доступность приложения

При развертывании на платформах приложения могут предоставлять информацию о своей доступности платформе, используя инфраструктуру, такую как Пробы Kubernetes. Spring Boot включает встроенную поддержку распространенных состояний доступности «жизнеспособности» и «готовности». Если вы используете поддержку «актуатора» Spring Boot, эти состояния экспонируются как группы конечных точек состояния.

Кроме того, вы также можете получить состояния доступности, внедрив интерфейс ApplicationAvailability в свои собственные бины.

1.6.1. Состояние жизнеспособности

Состояние «жизнеспособности» приложения указывает, позволяет ли его внутреннее состояние работать корректно или восстановиться само по себе, если оно в настоящее время не работает. Сломанное состояние «жизнеспособности» означает, что приложение находится в состоянии, из которого оно не может восстановиться, и инфраструктура должна перезапустить приложение.

В целом, состояние «жизнеспособности» не должно основываться на внешних проверках, таких как Проверки состояния. В противном случае сбои внешней системы (базы данных, веб-API, внешнего кэша) приведут к массовым перезапускам и каскадным сбоям на всей платформе.

Внутреннее состояние приложений Spring Boot в основном представлено контекстом Spring ApplicationContext. Если контекст приложения успешно запущен, Spring Boot предполагает, что приложение находится в валидном состоянии. Приложение считается живым, как только контекст был обновлён, см. Жизненный цикл приложения Spring Boot и связанные события приложения.

1.6.2. Состояние готовности

Состояние «готовности» приложения указывает, готово ли приложение обрабатывать трафик. Сбой состояния «готовности» сообщает платформе, что в настоящее время не следует направлять трафик в приложение. Это обычно происходит во время запуска, в то время как компоненты CommandLineRunner и ApplicationRunner обрабатываются, или в любое время, если приложение решает, что оно слишком занято для дополнительного трафика.

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

Задачи, ожидаемые во время запуска, должны выполняться компонентами CommandLineRunner и ApplicationRunner, а не с использованием обратных вызовов жизненного цикла компонента Spring, таких как @PostConstruct.

1.6.3. Управление состоянием доступности приложения

Компоненты приложения могут извлекать текущее состояние доступности в любое время, внедрив интерфейс ApplicationAvailability и вызвав методы на нём. Чаще всего приложения будут следить за обновлениями состояния или обновлять состояние приложения.

Например, мы можем экспортировать состояние «готовности» приложения в файл, чтобы «exec Probe» Kubernetes мог просмотреть этот файл:

Java
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class MyReadinessStateExporter {

    @EventListener
    public void onStateChange(AvailabilityChangeEvent<ReadinessState> event) {
        switch (event.getState()) {
            case ACCEPTING_TRAFFIC:
                // create file /tmp/healthy
                break;
            case REFUSING_TRAFFIC:
                // remove file /tmp/healthy
                break;
        }
    }

}
Kotlin
import org.springframework.boot.availability.AvailabilityChangeEvent
import org.springframework.boot.availability.ReadinessState
import org.springframework.context.event.EventListener
import org.springframework.stereotype.Component

@Component
class MyReadinessStateExporter {

    @EventListener
    fun onStateChange(event: AvailabilityChangeEvent<ReadinessState?>) {
        when (event.state) {
            ReadinessState.ACCEPTING_TRAFFIC -> {
                // create file /tmp/healthy
            }
            ReadinessState.REFUSING_TRAFFIC -> {
                // remove file /tmp/healthy
            }
            else -> {
                // ...
            }
        }
    }

}

Мы также можем обновить состояние приложения, когда приложение выходит из строя и не может восстановиться:

Java
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.LivenessState;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;

@Component
public class MyLocalCacheVerifier {

    private final ApplicationEventPublisher eventPublisher;

    public MyLocalCacheVerifier(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    public void checkLocalCache() {
        try {
            // ...
        }
        catch (CacheCompletelyBrokenException ex) {
            AvailabilityChangeEvent.publish(this.eventPublisher, ex, LivenessState.BROKEN);
        }
    }

}
Kotlin
import org.springframework.boot.availability.AvailabilityChangeEvent
import org.springframework.boot.availability.LivenessState
import org.springframework.context.ApplicationEventPublisher
import org.springframework.stereotype.Component

@Component
class MyLocalCacheVerifier(private val eventPublisher: ApplicationEventPublisher) {

    fun checkLocalCache() {
        try {
            // ...
        } catch (ex: CacheCompletelyBrokenException) {
            AvailabilityChangeEvent.publish(eventPublisher, ex, LivenessState.BROKEN)
        }
    }

}

Spring Boot предоставляет Пробы HTTP Kubernetes для "жизнеспособности" и "готовности" с конечными точками состояния Актуатора. Более подробную информацию о развертывании приложений Spring Boot на Kubernetes можно найти в соответствующем разделе.

1.7. События и слушатели приложения

Помимо обычных событий Spring Framework, таких как ContextRefreshedEvent, приложение Spring Boot также генерирует дополнительные события приложения.

Некоторые события фактически срабатывают до создания ApplicationContext, поэтому вы не можете зарегистрировать слушателя на них как @Bean. Вы можете зарегистрировать их с помощью метода SpringApplication.addListeners(…​) или метода SpringApplicationBuilder.listeners(…​).

Если вы хотите, чтобы эти слушатели регистрировались автоматически независимо от того, как создается приложение, вы можете добавить файл META-INF/spring.factories в свой проект и указать своего слушателя(ей) с помощью ключа org.springframework.context.ApplicationListener, как показано в следующем примере:

org.springframework.context.ApplicationListener=com.example.project.MyListener

События приложения отправляются в следующем порядке, поскольку ваше приложение выполняется:

  1. Отправляется событие ApplicationStartingEvent в начале выполнения, но до обработки, за исключением регистрации слушателей и инициализаторов.

  2. Отправляется событие ApplicationEnvironmentPreparedEvent, когда контекст Environment, который будет использоваться в контексте, известен, но до создания контекста.

  3. Отправляется событие ApplicationContextInitializedEvent, когда контекст ApplicationContext подготовлен, вызваны ApplicationContextInitializers, но до загрузки каких-либо определений бинов.

  4. Отправляется событие ApplicationPreparedEvent непосредственно перед началом обновления, но после загрузки определений бинов.

  5. Отправляется событие ApplicationStartedEvent после обновления контекста, но до вызова приложения и исполняемых файлов командной строки.

  6. Отправляется событие AvailabilityChangeEvent сразу после с LivenessState.CORRECT, чтобы указать, что приложение считается живым.

  7. Отправляется событие ApplicationReadyEvent после вызова приложения и исполняемых файлов командной строки.

  8. Отправляется событие AvailabilityChangeEvent сразу после с ReadinessState.ACCEPTING_TRAFFIC, чтобы указать, что приложение готово обслуживать запросы.

  9. Отправляется событие ApplicationFailedEvent, если при запуске возникла ошибка.

В приведенном выше списке указаны только события SpringApplicationEvent, которые связаны с SpringApplication. Кроме этих событий, также публикуются следующие события после ApplicationPreparedEvent и до ApplicationStartedEvent:

  • Отправляется событие WebServerInitializedEvent после готовности WebServer. ServletWebServerInitializedEvent и ReactiveWebServerInitializedEvent являются соответственно вариантами для сервлетной и реактивной моделей.

  • Отправляется событие ContextRefreshedEvent при обновлении ApplicationContext.

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

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

Чтобы слушатель мог отличать событие для своего контекста от события для дочернего контекста, он должен запросить внедрение своего контекста приложения, а затем сравнить внедренный контекст с контекстом события. Контекст можно внедрить, реализовав интерфейс ApplicationContextAware или, если слушатель является бином, используя @Autowired.

1.8. Веб-среда

Spring Boot пытается создать правильный тип веб-сервлета от вашего имени. Алгоритм определения веб-сервлета следующий:

  • Если присутствует Spring MVC, используется Spring MVC веб-сервлет.

  • Если Spring MVC отсутствует, а Spring WebFlux присутствует, используется Spring WebFlux веб-сервлет.

  • В противном случае, используется встроенный веб-сервлет.

Это означает, что если вы используете Spring MVC и новый веб-сервлет Spring WebFlux в одном приложении, Spring MVC будет использоваться по умолчанию. Вы можете легко переопределить это, вызвав соответствующий метод.

Также возможно полностью контролировать тип веб-сервлета, вызвав нужный метод.

Часто желательно вызвать соответствующий метод, когда Spring MVC используется в JUnit тесте.

1.9. Доступ к аргументам приложения

Если вам нужно получить доступ к аргументам приложения, переданным в приложение, вы можете инжектировать бины. Интерфейс позволяет получить доступ к исходным аргументам, а также к обработанным аргументам и параметрам, как показано в следующем примере:

Java
import java.util.List;

import org.springframework.boot.ApplicationArguments;
import org.springframework.stereotype.Component;

@Component
public class MyBean {

    public MyBean(ApplicationArguments args) {
        boolean debug = args.containsOption("debug");
        List<String> files = args.getNonOptionArgs();
        if (debug) {
            System.out.println(files);
        }
        // if run with "--debug logfile.txt" prints ["logfile.txt"]
    }

}
Kotlin
import org.springframework.boot.ApplicationArguments
import org.springframework.stereotype.Component

@Component
class MyBean(args: ApplicationArguments) {

    init {
        val debug = args.containsOption("debug")
        val files = args.nonOptionArgs
        if (debug) {
            println(files)
        }
        // if run with "--debug logfile.txt" prints ["logfile.txt"]
    }

}
Spring Boot также регистрирует бины с аргументами приложения в Spring контексте. Это позволяет инжектировать отдельные аргументы приложения с помощью аннотации.

1.10. Использование ApplicationRunner или CommandLineRunner

Если вам нужно выполнить определенный код после запуска приложения, вы можете реализовать интерфейсы или . Оба интерфейса работают одинаково и предлагают метод, который вызывается непосредственно перед завершением запуска приложения.

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

Интерфейс предоставляет доступ к аргументам приложения в виде массива строк, а интерфейс использует интерфейс, обсуждавшийся ранее. Следующий пример демонстрирует класс, реализующий метод:

Java
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class MyCommandLineRunner implements CommandLineRunner {

    @Override
    public void run(String... args) {
        // Do something...
    }

}
Kotlin
import org.springframework.boot.CommandLineRunner
import org.springframework.stereotype.Component

@Component
class MyCommandLineRunner : CommandLineRunner {

    override fun run(vararg args: String) {
        // Do something...
    }

}

Если определено несколько бинов или, которые должны вызываться в определенном порядке, вы можете дополнительно реализовать интерфейс или использовать аннотацию.

1.11. Выход из приложения

Каждое приложение регистрирует обработчик завершения работы с JVM, чтобы гарантировать, что приложение закрывается корректно при выходе. Все стандартные колбеки жизненного цикла Spring (такие как интерфейс или аннотация) могут быть использованы.

Кроме того, бины могут реализовать интерфейс, если они хотят возвратить определенный код выхода при вызове метода. Этот код выхода может быть передан методу для возврата в качестве кода состояния, как показано в следующем примере:

Java
import org.springframework.boot.ExitCodeGenerator;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class MyApplication {

    @Bean
    public ExitCodeGenerator exitCodeGenerator() {
        return () -> 42;
    }

    public static void main(String[] args) {
        System.exit(SpringApplication.exit(SpringApplication.run(MyApplication.class, args)));
    }

}
Kotlin
import org.springframework.boot.ExitCodeGenerator
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean

import kotlin.system.exitProcess

@SpringBootApplication
class MyApplication {

    @Bean
    fun exitCodeGenerator() = ExitCodeGenerator { 42 }

}

fun main(args: Array<String>) {
    exitProcess(SpringApplication.exit(
        runApplication<MyApplication>(*args)))
}

Также интерфейс может быть реализован исключениями. Когда возникает такое исключение, Spring Boot возвращает код выхода, предоставленный реализованным методом.

Если существует более одного генератора кода выхода, используется первый ненулевой код выхода. Для управления порядком вызова генераторов дополнительно реализуйте интерфейс или используйте аннотацию.

1.12. Функции администратора

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

Если вы хотите узнать, на каком HTTP порту запущено приложение, получите свойство с ключом.

1.13. Отслеживание запуска приложения

Во время запуска приложения и выполняют множество задач, связанных с жизненным циклом приложения, жизненным циклом бинов или даже обработкой событий приложения. С Spring Framework позволяет отслеживать последовательность запуска приложения с помощью объектов. Эти данные могут собираться для профилирования или для лучшего понимания процесса запуска приложения.

Вы можете выбрать реализацию при настройке экземпляра. Например, для использования , вы можете написать:

Java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(MyApplication.class);
        application.setApplicationStartup(new BufferingApplicationStartup(2048));
        application.run(args);
    }

}
Kotlin
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup
import org.springframework.boot.runApplication

@SpringBootApplication
class MyApplication

fun main(args: Array<String>) {
    runApplication<MyApplication>(*args) {
        applicationStartup = BufferingApplicationStartup(2048)
    }
}

Первая доступная реализация предоставляется Spring Framework. Она добавляет события, специфичные для Spring, в сессию Java Flight Recorder и предназначена для профилирования приложений и сопоставления их жизненного цикла контекста Spring с событиями JVM (такими как выделение памяти, сборка мусора, загрузка классов…​). После настройки вы можете записывать данные, запуская приложение с включенным Flight Recorder:

$ java -XX:StartFlightRecording:filename=recording.jfr,duration=10s -jar demo.jar

Spring Boot поставляется с вариантом; эта реализация предназначена для буферизации этапов запуска и их перенаправления в внешнюю систему метрик. Приложения могут запросить бин типа в любом компоненте.

Spring Boot также можно настроить для экспонирования конечной точки, которая предоставляет эту информацию в формате JSON.

2. Внешняя конфигурация

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

Значения свойств можно напрямую внедрять в ваши компоненты с помощью аннотации @Value, получать доступ через абстракцию Spring’s Environment или привязывать к структурированным объектам через @ConfigurationProperties.

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

  1. Значения по умолчанию (указанные путем установки SpringApplication.setDefaultProperties).

  2. @PropertySource аннотации на ваших @Configuration классах. Обратите внимание, что такие источники свойств не добавляются в Environment, пока контекст приложения не будет обновлён. Это происходит слишком поздно для настройки определённых свойств, таких как logging.* и spring.main.*, которые считываются до начала обновления.

  3. Данные конфигурации (например, файлы application.properties).

  4. RandomValuePropertySource, содержащий свойства только в random.*.

  5. Переменные среды ОС.

  6. Свойства системы Java (System.getProperties()).

  7. Атрибуты JNDI из java:comp/env.

  8. Параметры инициализации ServletContext.

  9. Параметры инициализации ServletConfig.

  10. Свойства из SPRING_APPLICATION_JSON (встроенный JSON в переменной среды или свойстве системы).

  11. Аргументы командной строки.

  12. properties атрибут в ваших тестах. Доступен в @SpringBootTest и аннотациях для тестирования конкретного фрагмента вашего приложения.

  13. @DynamicPropertySource аннотации в ваших тестах.

  14. @TestPropertySource аннотации в ваших тестах.

  15. Глобальные настройки Devtools в каталоге $HOME/.config/spring-boot, когда активен devtools.

Файлы данных конфигурации рассматриваются в следующем порядке:

  1. Свойства приложения упакованные внутри вашего jar (варианты application.properties и YAML).

  2. Свойства приложения, специфичные для профиля упакованные внутри вашего jar (варианты application-{profile}.properties и YAML).

  3. Свойства приложения вне вашего упакованного jar (варианты application.properties и YAML).

  4. Свойства приложения, специфичные для профиля вне вашего упакованного jar (варианты application-{profile}.properties и YAML).

Рекомендуется придерживаться одного формата для всего приложения. Если у вас есть файлы конфигурации с форматом .properties и YAML в одном месте, формат .properties имеет приоритет.
Если вы используете переменные среды вместо свойств системы, большинство операционных систем не позволяют имена ключей с точками, но вы можете использовать подчёркивания вместо (например, SPRING_CONFIG_NAME вместо spring.config.name). Подробнее см. Привязка из переменных среды.
Если ваше приложение работает в контейнере Servlet или сервере приложений, можно использовать свойства JNDI (в java:comp/env) или параметры инициализации контекста Servlet вместо или дополнительно к переменным среды или свойствам системы.

В качестве конкретного примера, предположим, что вы разрабатываете приложение @Component, которое использует свойство name, как показано в следующем примере:

Java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class MyBean {

    @Value("${name}")
    private String name;

    // ...

}
Kotlin
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component

@Component
class MyBean {

    @Value("\${name}")
    private val name: String? = null

    // ...

}

В вашем классе приложения (например, внутри вашего jar) может быть файл application.properties, который предоставляет разумное значение свойства по умолчанию для name. При запуске в новой среде можно предоставить файл application.properties вне вашего jar, который переопределит name. Для разовых тестов вы можете запустить приложение с определенным ключом командной строки (например, java -jar app.jar --name="Spring").

Конечные точки env и configprops могут быть полезны для определения причины, по которой свойство имеет определённое значение. Вы можете использовать эти две конечные точки для диагностики неожиданных значений свойств. Подробнее см. раздел "Готовые к использованию в производстве функции".

2.1. Доступ к свойствам командной строки

По умолчанию, SpringApplication преобразует любые аргументы командной строки (т.е., аргументы, начинающиеся с --, такие как --server.port=9000) в свойства и добавляет их к Spring Environment. Как упоминалось ранее, свойства командной строки всегда имеют приоритет над источниками свойств, основанными на файлах.

Если вы не хотите, чтобы свойства командной строки добавлялись в Environment, вы можете отключить их с помощью SpringApplication.setAddCommandLineProperties(false).

2.2. Свойства приложения в формате JSON

Переменные окружения и свойства системы часто имеют ограничения, из-за которых некоторые имена свойств нельзя использовать. Чтобы помочь с этим, Spring Boot позволяет вам закодировать блок свойств в одну JSON-структуру.

При запуске вашего приложения, все spring.application.json или SPRING_APPLICATION_JSON свойства будут обработаны и добавлены в Environment.

Например, свойство SPRING_APPLICATION_JSON можно передать в командной строке в оболочке UN*X как переменную окружения:

$ SPRING_APPLICATION_JSON='{"my":{"name":"test"}}' java -jar myapp.jar

В приведенном примере вы получите my.name=test в Spring Environment.

Тот же JSON также можно передать в качестве свойства системы:

$ java -Dspring.application.json='{"my":{"name":"test"}}' -jar myapp.jar

Или вы можете передать JSON с помощью аргумента командной строки:

$ java -jar myapp.jar --spring.application.json='{"my":{"name":"test"}}'

Если вы развертываете приложение на классическом сервере приложений, вы также можете использовать переменную JNDI с именем java:comp/env/spring.application.json.

Хотя значения null из JSON будут добавлены в результирующий источник свойств, PropertySourcesPropertyResolver обрабатывает null свойства как отсутствующие значения. Это означает, что JSON не может переопределить свойства из источников свойств более низкого порядка с помощью значения null.

2.3. Внешние свойства приложения

Spring Boot автоматически найдет и загрузит файлы application.properties и application.yaml из следующих расположений при запуске приложения:

  1. Из класса

    1. Корень класса

    2. Пакет класса /config

  2. Из текущей директории

    1. Текущая директория

    2. Поддиректория config/ в текущей директории

    3. Непосредственные поддиректории поддиректории config/

Список упорядочен по приоритету (значения из элементов ниже переопределяют предыдущие). Документы из загруженных файлов добавляются как PropertySources в Spring Environment.

Если вам не нравится имя файла конфигурации application, вы можете переключиться на другое имя, указав свойство среды spring.config.name. Например, чтобы найти файлы myproject.properties и myproject.yaml, вы можете запустить приложение следующим образом:

$ java -jar myproject.jar --spring.config.name=myproject

Вы также можете указать явное расположение, используя свойство среды spring.config.location. Это свойство принимает список, разделенный запятыми, одного или нескольких расположений для проверки.

Следующий пример показывает, как указать два разных файла:

$ java -jar myproject.jar --spring.config.location=\
    optional:classpath:/default.properties,\
    optional:classpath:/override.properties
Используйте префикс optional:, если расположения являются необязательными и вы не возражаете, если они не существуют.
spring.config.name, spring.config.location, и spring.config.additional-location используются очень рано, чтобы определить, какие файлы нужно загрузить. Они должны быть определены как свойство среды (обычно переменная среды ОС, системная переменная или аргумент командной строки).

Если spring.config.location содержит каталоги (в отличие от файлов), они должны заканчиваться на /. Во время выполнения к ним будут добавлены имена, сгенерированные из spring.config.name, прежде чем они будут загружены. Файлы, указанные в spring.config.location, импортируются напрямую.

Значения расположения каталогов и файлов также расширяются для проверки файлов, специфичных для профиля. Например, если у вас есть spring.config.location classpath:myconfig.properties, будут также загружены соответствующие файлы classpath:myconfig-<profile>.properties.

В большинстве случаев каждый элемент spring.config.location, который вы добавляете, будет ссылаться на один файл или каталог. Расположения обрабатываются в том порядке, в котором они определены, и последующие могут переопределять значения предыдущих.

Если у вас сложная настройка расположений и вы используете файлы конфигурации, специфичные для профиля, вам может потребоваться предоставить дополнительные подсказки, чтобы Spring Boot знал, как их сгруппировать. Группа расположений — это набор расположений, которые рассматриваются на одном уровне. Например, вы можете сгруппировать все расположения класса, а затем все внешние расположения. Элементы в группе расположений должны быть разделены символом ;. Подробности см. в разделе «Файлы, специфичные для профиля».

Расположения, настроенные с помощью spring.config.location, заменяют стандартные расположения. Например, если spring.config.location настроено со значением optional:classpath:/custom-config/,optional:file:./custom-config/, полный набор рассматриваемых расположений:

  1. optional:classpath:custom-config/

  2. optional:file:./custom-config/

Если вы предпочитаете добавить дополнительные расположения вместо их замены, вы можете использовать spring.config.additional-location. Свойства, загруженные из дополнительных расположений, могут переопределять свойства в стандартных расположениях. Например, если spring.config.additional-location настроено со значением optional:classpath:/custom-config/,optional:file:./custom-config/, полный набор рассматриваемых расположений:

  1. optional:classpath:/;optional:classpath:/config/

  2. optional:file:./;optional:file:./config/;optional:file:./config/*/

  3. optional:classpath:custom-config/

  4. optional:file:./custom-config/

Такой порядок поиска позволяет указать значения по умолчанию в одном файле конфигурации, а затем выборочно переопределить их в другом. Вы можете предоставить значения по умолчанию для своего приложения в application.properties (или любом другом базовом имени, которое вы выберете с помощью spring.config.name) в одном из стандартных расположений. Эти значения по умолчанию можно переопределить во время выполнения с помощью другого файла, расположенного в одном из пользовательских расположений.

2.3.1. Необязательные расположения

По умолчанию, если указанное расположение данных конфигурации не существует, Spring Boot выбросит исключение ConfigDataLocationNotFoundException, и ваше приложение не запустится.

Если вы хотите указать расположение, но не возражаете, если оно не всегда существует, вы можете использовать префикс optional:. Вы можете использовать этот префикс со свойствами spring.config.location и spring.config.additional-location, а также с объявлениями spring.config.import.

Например, значение spring.config.import optional:file:./myconfig.properties позволяет вашему приложению запускаться, даже если файл myconfig.properties отсутствует.

Если вы хотите игнорировать все ConfigDataLocationNotFoundExceptions и всегда продолжать запуск приложения, вы можете использовать свойство spring.config.on-not-found. Установите значение на ignore с помощью SpringApplication.setDefaultProperties(…​) или системной/средовой переменной.

2.3.2. Расположения с подстановочными знаками

Если расположение файла конфигурации включает символ * для последнего сегмента пути, оно считается расположением с подстановочными знаками. Подстановочные знаки расширяются при загрузке конфигурации, поэтому проверяются также непосредственные подкаталоги. Расположения с подстановочными знаками особенно полезны в среде, например, Kubernetes, когда есть несколько источников свойств конфигурации.

Например, если у вас есть конфигурация Redis и конфигурация MySQL, вы можете захотеть сохранить эти две части конфигурации раздельно, при этом потребовав, чтобы обе были присутствовать в файле application.properties. Это может привести к двум отдельным файлам application.properties, размещенным в разных расположениях, таких как /config/redis/application.properties и /config/mysql/application.properties. В таком случае, наличие расположения с подстановочным знаком config/*/ приведет к обработке обоих файлов.

По умолчанию Spring Boot включает config/*/ в стандартные расположения поиска. Это означает, что будут проверяться все подкаталоги каталога /config вне вашего JAR-файла.

Вы можете использовать расположения с подстановочными знаками самостоятельно с помощью свойств spring.config.location и spring.config.additional-location.

Расположение с подстановочными знаками должно содержать только один * и заканчиваться на */ для расположений поиска, которые являются каталогами, или */<filename> для расположений поиска, которые являются файлами. Расположения с подстановочными знаками сортируются по алфавиту на основе абсолютного пути к именам файлов.
Расположения с подстановочными знаками работают только с внешними каталогами. Вы не можете использовать подстановочные знаки в расположениях classpath:.

2.3.3. Файлы, специфичные для профиля

Помимо файлов свойств application, Spring Boot также попытается загрузить файлы, специфичные для профиля, используя соглашение об именовании application-{profile}. Например, если ваше приложение активирует профиль с именем prod и использует файлы YAML, то будут рассмотрены как application.yaml, так и application-prod.yaml.

Свойства, специфичные для профиля, загружаются из тех же мест, что и стандартные файлы application.properties, при этом файлы, специфичные для профиля, всегда переопределяют неспецифичные. Если указано несколько профилей, применяется стратегия «последнее значение выигрывает». Например, если профили prod,live указаны свойством spring.profiles.active, значения в application-prod.properties могут быть переопределены значениями из application-live.properties.

Стратегия «последнее значение выигрывает» применяется на уровне группы расположений. Файл spring.config.location с classpath:/cfg/,classpath:/ext/ не будет иметь таких же правил переопределения, как classpath:/cfg/;classpath:/ext/.

Например, продолжая наш пример с prod,live, у нас могут быть следующие файлы:

/cfg
  application-live.properties
/ext
  application-live.properties
  application-prod.properties

Когда у нас есть spring.config.location с classpath:/cfg/,classpath:/ext/, мы обрабатываем все файлы /cfg перед всеми файлами /ext:

  1. /cfg/application-live.properties

  2. /ext/application-prod.properties

  3. /ext/application-live.properties

Когда у нас есть classpath:/cfg/;classpath:/ext/ вместо этого (с разделителем ;), мы обрабатываем /cfg и /ext на одном уровне:

  1. /ext/application-prod.properties

  2. /cfg/application-live.properties

  3. /ext/application-live.properties

У Environment есть набор профилей по умолчанию (по умолчанию, [default]), которые используются, если активные профили не заданы. Другими словами, если профили не активированы явно, то учитываются свойства из application-default.

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

2.3.4. Импорт дополнительных данных

Свойства приложения могут импортировать дополнительные данные конфигурации из других мест, используя свойство spring.config.import. Импорты обрабатываются по мере обнаружения и рассматриваются как дополнительные документы, вставленные сразу под документом, который объявляет импорт.

Например, в вашем классе application.properties может быть следующий файл:

Свойства
spring.application.name=myapp
spring.config.import=optional:file:./dev.properties
Yaml
spring:
  application:
    name: "myapp"
  config:
    import: "optional:file:./dev.properties"

Это вызовет импорт файла dev.properties в текущей директории (если такой файл существует). Значения из импортированного dev.properties будут иметь приоритет над файлом, который инициировал импорт. В приведённом примере dev.properties может переопределить значение spring.application.name.

Импорт будет осуществлен только один раз, независимо от того, сколько раз он объявлен. Порядок объявления импорта внутри одного документа в файлах свойств/YAML не имеет значения. Например, два примера ниже дают один и тот же результат:

Свойства
spring.config.import=my.properties
my.property=value
Yaml
spring:
  config:
    import: "my.properties"
my:
  property: "value"
Свойства
my.property=value
spring.config.import=my.properties
Yaml
my:
  property: "value"
spring:
  config:
    import: "my.properties"

В обоих примерах значения из файла my.properties будут иметь приоритет над файлом, который инициировал импорт.

Несколько расположений могут быть указаны под одним ключом spring.config.import. Расположения будут обрабатываться в порядке их определения, а более поздние импорты будут иметь приоритет.

При необходимости, варианты, специфичные для профиля, также рассматриваются для импорта. Приведённый пример будет импортировать как my.properties, так и все my-<profile>.properties варианты.

Spring Boot включает подключаемый API, который позволяет поддерживать различные адреса расположения. По умолчанию поддерживаются импорт Java Properties, YAML и «деревья конфигурации».

Библиотеки сторонних разработчиков могут предложить поддержку дополнительных технологий (файлы не обязаны быть локальными). Например, можно представить данные конфигурации из внешних хранилищ, таких как Consul, Apache ZooKeeper или Netflix Archaius.

Если вы хотите поддержать свои собственные расположения, обратитесь к классам ConfigDataLocationResolver и ConfigDataLoader в пакете org.springframework.boot.context.config.

2.3.5. Импорт файлов без расширения

Некоторые облачные платформы не могут добавить расширение файла к объёмно смонтированным файлам. Чтобы импортировать эти файлы без расширения, необходимо дать Spring Boot подсказку о том, как их загружать. Это можно сделать, поместив подсказку расширения в квадратные скобки.

Например, предположим, что у вас есть файл /etc/config/myconfig, который вы хотите импортировать как YAML. Вы можете импортировать его из вашего application.properties, используя следующее:

Свойства
spring.config.import=file:/etc/config/myconfig[.yaml]
Yaml
spring:
  config:
    import: "file:/etc/config/myconfig[.yaml]"

2.3.6. Использование деревьев конфигурации

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

В качестве альтернативы переменным окружения многие облачные платформы теперь позволяют отображать конфигурацию в смонтированных томах данных. Например, Kubernetes может монтировать тома как ConfigMaps, так и Secrets.

Существует два распространённых шаблона монтирования томов:

  1. Один файл содержит полный набор свойств (обычно в формате YAML).

  2. Несколько файлов записываются в дереве каталогов, при этом имя файла становится «ключом», а содержимое — «значением».

В первом случае вы можете импортировать YAML- или Properties-файл напрямую, используя spring.config.import, как описано выше. Во втором случае необходимо использовать префикс configtree:, чтобы Spring Boot знал, что ему нужно экспонировать все файлы как свойства.

В качестве примера, предположим, что Kubernetes смонтировал следующий том:

etc/
  config/
    myapp/
      username
      password

Содержимое файла username будет значением конфигурации, а содержимое файла password — секретом.

Для импорта этих свойств вы можете добавить следующее в свой файл application.properties или application.yaml:

Свойства
spring.config.import=optional:configtree:/etc/config/
Yaml
spring:
  config:
    import: "optional:configtree:/etc/config/"

Затем вы можете получить доступ к или внедрить свойства myapp.username и myapp.password из Environment обычным способом.

Имена папок и файлов в дереве конфигурации образуют имя свойства. В приведенном выше примере, чтобы получить доступ к свойствам как username и password, можно установить spring.config.import в optional:configtree:/etc/config/myapp.
Имена файлов с использованием точечной нотации также корректно отображаются. Например, в примере выше, файл с именем myapp.username в каталоге /etc/config приведет к свойству myapp.username в Environment.
Значения дерева конфигурации могут быть привязаны к типам строкового String и byte[] в зависимости от ожидаемого содержимого.

Если у вас несколько деревьев конфигурации для импорта из одной родительской папки, вы можете использовать сокращение с подстановочным знаком. Любое расположение configtree:, которое заканчивается на /*/, импортирует всех непосредственных потомков как деревья конфигурации. Как и при импорте без подстановочного знака, имена папок и файлов в каждом дереве конфигурации образуют имя свойства.

Например, если данный том:

etc/
  config/
    dbconfig/
      db/
        username
        password
    mqconfig/
      mq/
        username
        password

Вы можете использовать configtree:/etc/config/*/ в качестве расположения импорта:

Свойства
spring.config.import=optional:configtree:/etc/config/*/
Yaml
spring:
  config:
    import: "optional:configtree:/etc/config/*/"

Это добавит свойства db.username, db.password, mq.username и mq.password.

Каталоги, загруженные с использованием подстановочного знака, сортируются в алфавитном порядке. Если вам нужен другой порядок, вы должны перечислить каждое расположение как отдельный импорт.

Деревья конфигурации также могут использоваться для Docker-секретов. Когда служба Docker swarm получает доступ к секрету, секрет монтируется в контейнер. Например, если секрет с именем db.password смонтирован по пути /run/secrets/, вы можете сделать db.password доступным для среды Spring следующим образом:

Свойства
spring.config.import=optional:configtree:/run/secrets/
Yaml
spring:
  config:
    import: "optional:configtree:/run/secrets/"

2.3.7. Подстановки свойств

Значения в файлах application.properties и application.yaml фильтруются через существующие Environment при использовании, поэтому вы можете ссылаться на ранее определенные значения (например, из системных свойств или переменных окружения). Стандартный синтаксис подстановки свойств ${name} можно использовать где угодно в значении. Подстановки свойств также могут указывать значение по умолчанию, используя : для разделения значения по умолчанию и имени свойства, например ${name:default}.

Использование подстановок с и без значений по умолчанию показано в следующем примере:

Свойства
app.name=MyApp
app.description=${app.name} is a Spring Boot application written by ${username:Unknown}
Yaml
app:
  name: "MyApp"
  description: "${app.name} is a Spring Boot application written by ${username:Unknown}"

Предполагая, что свойство username не было установлено в другом месте, app.description будет иметь значение MyApp is a Spring Boot application written by Unknown.

Вы всегда должны ссылаться на имена свойств в подстановке в канонической форме (кебаб-кейс, используя только строчные буквы). Это позволит Spring Boot использовать ту же логику, что и при расслабленной привязке @ConfigurationProperties.

Например, ${demo.item-price} подберет формы demo.item-price и demo.itemPrice из файла application.properties, а также DEMO_ITEMPRICE из системной среды. Если вы использовали бы ${demo.itemPrice}, demo.item-price и DEMO_ITEMPRICE не рассматривались бы.

Этот метод также можно использовать для создания «кратких» вариантов существующих свойств Spring Boot. Подробности см. в разделе howto.html.

2.3.8. Работа с файлами, содержащими несколько документов

Spring Boot позволяет разделять один физический файл на несколько логических документов, каждый из которых добавляется независимо. Документы обрабатываются в порядке следования сверху вниз. Более поздние документы могут перезаписывать свойства, определённые в предыдущих.

Для файлов application.yaml используется стандартный синтаксис YAML для нескольких документов. Три последовательных дефиса обозначают конец одного документа и начало следующего.

Например, в следующем файле два логических документа:

spring:
  application:
    name: "MyApp"
---
spring:
  application:
    name: "MyCloudApp"
  config:
    activate:
      on-cloud-platform: "kubernetes"

Для файлов application.properties используется специальный комментарий #--- или !--- для обозначения разбиения документов:

spring.application.name=MyApp
#---
spring.application.name=MyCloudApp
spring.config.activate.on-cloud-platform=kubernetes
Разделители файлов свойств не должны содержать начальных пробелов и должны состоять ровно из трех дефисов. Строки непосредственно перед и после разделителя не должны содержать того же префикса комментария.
Файлы свойств с несколькими документами часто используются вместе с свойствами активации, такими как spring.config.activate.on-profile. Подробности см. в следующем разделе.
Файлы свойств с несколькими документами нельзя загрузить с помощью аннотаций @PropertySource или @TestPropertySource.

2.3.9. Свойства активации

Иногда бывает полезно активировать заданный набор свойств только при выполнении определенных условий. Например, у вас могут быть свойства, относящиеся только к активному профилю.

Вы можете условно активировать документ свойств, используя spring.config.activate.*.

Доступны следующие свойства активации:

Таблица 2. свойства активации
Свойство Примечание

on-profile

Выражение профиля, которое должно соответствовать для активации документа.

on-cloud-platform

CloudPlatform, которое должно быть обнаружено для активации документа.

Например, следующее указывает, что второй документ активен только при работе в Kubernetes и только тогда, когда активны профили «prod» или «staging»:

Свойства
myprop=always-set
#---
spring.config.activate.on-cloud-platform=kubernetes
spring.config.activate.on-profile=prod | staging
myotherprop=sometimes-set
Yaml
myprop:
  "always-set"
---
spring:
  config:
    activate:
      on-cloud-platform: "kubernetes"
      on-profile: "prod | staging"
myotherprop: "sometimes-set"

2.4. Шифрование свойств

Spring Boot не предоставляет встроенной поддержки шифрования значений свойств, однако он предоставляет необходимые точки подключения для изменения значений, содержащихся в Spring Environment. Интерфейс EnvironmentPostProcessor позволяет манипулировать Environment перед запуском приложения. Подробности см. в howto.html.

Если вам нужен безопасный способ хранения учетных данных и паролей, проект Spring Cloud Vault предоставляет поддержку хранения внешних настроек в HashiCorp Vault.

2.5. Работа с YAML

YAML — это надмножество JSON и, как таковое, является удобным форматом для задания иерархических данных конфигурации. Класс SpringApplication автоматически поддерживает YAML в качестве альтернативы свойствам, когда у вас есть библиотека SnakeYAML в вашем классе.

Если вы используете «Starters», SnakeYAML автоматически предоставляется spring-boot-starter.

2.5.1. Сопоставление YAML со свойствами

Документы YAML необходимо преобразовать из иерархического формата в плоскую структуру, которую можно использовать с Spring Environment. Например, рассмотрим следующий документ YAML:

environments:
  dev:
    url: "https://dev.example.com"
    name: "Developer Setup"
  prod:
    url: "https://another.example.com"
    name: "My Cool App"

Для доступа к этим свойствам из Environment они будут сглажены следующим образом:

environments.dev.url=https://dev.example.com
environments.dev.name=Developer Setup
environments.prod.url=https://another.example.com
environments.prod.name=My Cool App

Аналогично, списки YAML также необходимо сглаживать. Они представлены в качестве ключей свойств с [index] дериференцировщиками. Например, рассмотрим следующий YAML:

my:
 servers:
 - "dev.example.com"
 - "another.example.com"

Представленный пример будет преобразован в следующие свойства:

my.servers[0]=dev.example.com
my.servers[1]=another.example.com
Свойства, использующие [index] нотацию, могут быть связаны с Java List или Set объектами с помощью класса Spring Boot Binder. Подробнее см. раздел «Безопасные свойства конфигурации» ниже.
Файлы YAML не могут быть загружены с помощью аннотаций @PropertySource или @TestPropertySource. Поэтому, в случае необходимости загрузки значений таким образом, вам нужно использовать файл свойств.

2.5.2. Прямая загрузка YAML

Spring Framework предоставляет два удобных класса, которые могут использоваться для загрузки документов YAML. YamlPropertiesFactoryBean загружает YAML как Properties, а YamlMapFactoryBean загружает YAML как Map.

Вы также можете использовать класс YamlPropertySourceLoader, если хотите загрузить YAML как Spring PropertySource.

2.6. Настройка случайных значений

RandomValuePropertySource полезно для вставки случайных значений (например, в секреты или тестовые случаи). Он может генерировать целые числа, long, uuid или строки, как показано в следующем примере:

Свойства
my.secret=${random.value}
my.number=${random.int}
my.bignumber=${random.long}
my.uuid=${random.uuid}
my.number-less-than-ten=${random.int(10)}
my.number-in-range=${random.int[1024,65536]}
Yaml
my:
  secret: "${random.value}"
  number: "${random.int}"
  bignumber: "${random.long}"
  uuid: "${random.uuid}"
  number-less-than-ten: "${random.int(10)}"
  number-in-range: "${random.int[1024,65536]}"

Синтаксис random.int* — OPEN value (,max) CLOSE, где OPEN,CLOSE — любые символы, а value,max — целые числа. Если max предоставлено, то value — минимальное значение, а max — максимальное значение (исключая его).

2.7. Настройка свойств системной среды

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

Например, если вы установите префикс на input, свойство, такое как remote.timeout, также будет разрешено как input.remote.timeout в системной среде.

2.8. Безопасные конфигурационные свойства

Использование аннотации @Value("${property}") для инъекции свойств конфигурации может быть неудобным, особенно при работе с несколькими свойствами или иерархическими данными. Spring Boot предоставляет альтернативный метод работы со свойствами, позволяющий сильно типизированным объектам управлять и проверять конфигурацию приложения.

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

2.8.1. Связывание свойств JavaBean

Можно связать объект, объявляющий стандартные свойства JavaBean, как показано в следующем примере:

Java
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("my.service")
public class MyProperties {

    private boolean enabled;

    private InetAddress remoteAddress;

    private final Security security = new Security();

    // getters / setters...

    public boolean isEnabled() {
        return this.enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public InetAddress getRemoteAddress() {
        return this.remoteAddress;
    }

    public void setRemoteAddress(InetAddress remoteAddress) {
        this.remoteAddress = remoteAddress;
    }

    public Security getSecurity() {
        return this.security;
    }

    public static class Security {

        private String username;

        private String password;

        private List<String> roles = new ArrayList<>(Collections.singleton("USER"));

        // getters / setters...

        public String getUsername() {
            return this.username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

        public String getPassword() {
            return this.password;
        }

        public void setPassword(String password) {
            this.password = password;
        }

        public List<String> getRoles() {
            return this.roles;
        }

        public void setRoles(List<String> roles) {
            this.roles = roles;
        }

    }

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties
import java.net.InetAddress

@ConfigurationProperties("my.service")
class MyProperties {

    var isEnabled = false

    var remoteAddress: InetAddress? = null

    val security = Security()

    class Security {

        var username: String? = null

        var password: String? = null

        var roles: List<String> = ArrayList(setOf("USER"))

    }

}

Предыдущий POJO определяет следующие свойства:

  • my.service.enabled со значением false по умолчанию.

  • my.service.remote-address, с типом, который может быть преобразован из String.

  • my.service.security.username, со вложенным объектом "security", имя которого определяется именем свойства. В частности, тип там вообще не используется и мог бы быть SecurityProperties.

  • my.service.security.password.

  • my.service.security.roles, с набором String, который по умолчанию равен USER.

Свойства, сопоставляемые с классами @ConfigurationProperties, доступными в Spring Boot, которые настраиваются через файлы свойств, файлы YAML, переменные среды и другие механизмы, являются частью API, но методы доступа (геттеры/сеттеры) самого класса не предназначены для прямого использования.

Такая организация полагается на конструктор по умолчанию, и геттеры и сеттеры обычно обязательны, так как связывание происходит через стандартные описатели свойств Java Beans, как и в Spring MVC. Сеттер может быть опущен в следующих случаях:

  • Массивы, постольку, поскольку они инициализированы, нуждаются в геттере, но не обязательно в сеттере, так как они могут быть изменены связующим элементом.

  • Коллекции и массивы могут быть обработаны либо по индексу (обычно с YAML), либо с использованием одного значения, разделенного запятыми (свойства). В последнем случае сеттер обязателен. Рекомендуется всегда добавлять сеттер для таких типов. Если вы инициализируете коллекцию, убедитесь, что она не неизменяема (как в предыдущем примере).

  • Если вложенные свойства POJO инициализированы (например, поле Security в предыдущем примере), сеттер не требуется. Если вы хотите, чтобы связующий элемент создавал экземпляр на лету, используя его конструктор по умолчанию, вам нужен сеттер.

Некоторые люди используют Project Lombok для автоматического добавления геттеров и сеттеров. Убедитесь, что Lombok не генерирует какой-либо определенный конструктор для такого типа, так как он автоматически используется контейнером для создания объекта.

Наконец, учитываются только стандартные свойства Java Bean, и привязка к статическим свойствам не поддерживается.

2.8.2. Связывание по конструктору

Пример в предыдущем разделе можно переписать в неизменяемом виде, как показано в следующем примере:

Java
import java.net.InetAddress;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;

@ConfigurationProperties("my.service")
public class MyProperties {

    // fields...

    private final boolean enabled;

    private final InetAddress remoteAddress;

    private final Security security;

    public MyProperties(boolean enabled, InetAddress remoteAddress, Security security) {
        this.enabled = enabled;
        this.remoteAddress = remoteAddress;
        this.security = security;
    }

    // getters...

    public boolean isEnabled() {
        return this.enabled;
    }

    public InetAddress getRemoteAddress() {
        return this.remoteAddress;
    }

    public Security getSecurity() {
        return this.security;
    }

    public static class Security {

        // fields...

        private final String username;

        private final String password;

        private final List<String> roles;

        public Security(String username, String password, @DefaultValue("USER") List<String> roles) {
            this.username = username;
            this.password = password;
            this.roles = roles;
        }

        // getters...

        public String getUsername() {
            return this.username;
        }

        public String getPassword() {
            return this.password;
        }

        public List<String> getRoles() {
            return this.roles;
        }

    }

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.DefaultValue
import java.net.InetAddress

@ConfigurationProperties("my.service")
class MyProperties(val enabled: Boolean, val remoteAddress: InetAddress,
        val security: Security) {

    class Security(val username: String, val password: String,
            @param:DefaultValue("USER") val roles: List<String>)

}

В этой настройке наличие единственного параметризованного конструктора подразумевает использование связывания по конструктору. Это означает, что связующий элемент найдет конструктор с параметрами, которые вы хотите связать. Если у вашего класса несколько конструкторов, аннотация @ConstructorBinding может использоваться для указания конструктора, который нужно использовать для связывания по конструктору. Чтобы отключить связывание по конструктору для класса с единственным параметризованным конструктором, конструктор должен быть аннотирован с помощью @Autowired. Связывание по конструктору можно использовать с записями. Если ваша запись не имеет нескольких конструкторов, нет необходимости использовать @ConstructorBinding.

Вложенные члены класса, связанного по конструктору (например, Security в приведенном выше примере), также будут связаны через свой конструктор.

Значения по умолчанию можно задать с помощью @DefaultValue для параметров конструктора и компонентов записей. Сервис преобразования будет применен для преобразования аннотированного String значения в целевой тип отсутствующего свойства.

Если обратиться к предыдущему примеру, если ни одно свойство не привязано к Security, экземпляр MyProperties будет содержать значение null для security. Чтобы он содержал непустой экземпляр Security даже когда ни одно свойство не привязано к нему (при использовании Kotlin, это потребует параметров username и password Security быть объявлены как необязательные, поскольку у них нет значений по умолчанию), используйте пустую аннотацию @DefaultValue:

Java
public MyProperties(boolean enabled, InetAddress remoteAddress, @DefaultValue Security security) {
    this.enabled = enabled;
    this.remoteAddress = remoteAddress;
    this.security = security;
}
Kotlin
class MyProperties(val enabled: Boolean, val remoteAddress: InetAddress,
        @DefaultValue val security: Security) {

    class Security(val username: String?, val password: String?,
            @param:DefaultValue("USER") val roles: List<String>)

}
Для использования связывания по конструктору класс должен быть включен с помощью @EnableConfigurationProperties или сканирования свойств конфигурации. Вы не можете использовать связывание по конструктору с компонентами, которые создаются стандартными механизмами Spring (например, @Component компоненты, компоненты, созданные с помощью методов @Bean или загруженные с помощью @Import).
Для использования связывания по конструктору в образце родного кода класс должен быть скомпилирован с -parameters. Это произойдет автоматически, если вы используете плагин Spring Boot Gradle или Maven и spring-boot-starter-parent.
Использование java.util.Optional с @ConfigurationProperties не рекомендуется, так как оно предназначено в первую очередь для использования в качестве возвращаемого типа. Поэтому оно не подходит для инъекции свойств конфигурации. Для согласованности со свойствами других типов, если вы объявляете свойство Optional и оно не имеет значения, то будет привязано null, а не пустое Optional.

2.8.3. Включение типов, аннотированных с помощью @ConfigurationProperties

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

Иногда классы, аннотированные с помощью @ConfigurationProperties, могут не подходить для сканирования, например, если вы разрабатываете собственные автоконфигурации или хотите включить их условно. В таких случаях укажите список типов для обработки с помощью аннотации @EnableConfigurationProperties. Это можно сделать в любом классе @Configuration, как показано в следующем примере:

Java
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SomeProperties.class)
public class MyConfiguration {

}
Kotlin
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SomeProperties::class)
class MyConfiguration
Java
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("some.properties")
public class SomeProperties {

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties("some.properties")
class SomeProperties

Чтобы использовать сканирование свойств конфигурации, добавьте аннотацию @ConfigurationPropertiesScan к своему приложению. Обычно она добавляется к главному классу приложения, аннотированному с помощью @SpringBootApplication, но ее можно добавить к любому классу @Configuration. По умолчанию сканирование будет выполняться из пакета класса, который объявляет аннотацию. Если вы хотите определить конкретные пакеты для сканирования, вы можете сделать это, как показано в следующем примере:

Java
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;

@SpringBootApplication
@ConfigurationPropertiesScan({ "com.example.app", "com.example.another" })
public class MyApplication {

}
Kotlin
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.properties.ConfigurationPropertiesScan

@SpringBootApplication
@ConfigurationPropertiesScan("com.example.app", "com.example.another")
class MyApplication

Когда компонент @ConfigurationProperties регистрируется с помощью сканирования свойств конфигурации или через @EnableConfigurationProperties, компонент имеет стандартное имя: <prefix>-<fqn>, где <prefix> — префикс ключа среды, указанный в аннотации @ConfigurationProperties, а <fqn> — полное имя компонента.

Предполагая, что он находится в пакете com.example.app, имя компонента для примера SomeProperties равно some.properties-com.example.app.SomeProperties.

Рекомендуется, чтобы @ConfigurationProperties работал только со средой и, в частности, не инжектировал другие компоненты из контекста. Для особых случаев можно использовать инъекцию через сеттер или любой из интерфейсов *Aware, предоставляемых фреймворком (например, EnvironmentAware, если вам нужен доступ к Environment). Если вы все же хотите инжектировать другие компоненты с помощью конструктора, компонент конфигурации свойств должен быть аннотирован с помощью @Component и использовать привязку свойств на основе JavaBean.

2.8.4. Использование типов, аннотированных с помощью @ConfigurationProperties

Этот стиль конфигурации особенно хорошо работает с внешней YAML-конфигурацией, как показано в следующем примере:

my:
  service:
    remote-address: 192.168.1.1
    security:
      username: "admin"
      roles:
      - "USER"
      - "ADMIN"

Чтобы работать с @ConfigurationProperties бинсами, вы можете вводить их так же, как и любые другие бинсы, как показано в следующем примере:

Java
import org.springframework.stereotype.Service;

@Service
public class MyService {

    private final MyProperties properties;

    public MyService(MyProperties properties) {
        this.properties = properties;
    }

    public void openConnection() {
        Server server = new Server(this.properties.getRemoteAddress());
        server.start();
        // ...
    }

    // ...

}
Kotlin
import org.springframework.stereotype.Service

@Service
class MyService(val properties: MyProperties) {

    fun openConnection() {
        val server = Server(properties.remoteAddress)
        server.start()
        // ...
    }

    // ...

}
Использование @ConfigurationProperties также позволяет генерировать файлы метаданных, которые могут использоваться IDE для предложения автодополнения для ваших собственных ключей. Подробности см. в приложении.

2.8.5. Конфигурация сторонних библиотек

Помимо использования @ConfigurationProperties для аннотирования класса, вы также можете использовать его для общедоступных @Bean методов. Это может быть особенно полезно, когда вы хотите привязать свойства к компонентам сторонних библиотек, которые находятся вне вашего контроля.

Для настройки бинса из Environment свойств добавьте @ConfigurationProperties к его регистрации бинсов, как показано в следующем примере:

Java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class ThirdPartyConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "another")
    public AnotherComponent anotherComponent() {
        return new AnotherComponent();
    }

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class ThirdPartyConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "another")
    fun anotherComponent(): AnotherComponent = AnotherComponent()

}

Любое свойство JavaBean, определённое с префиксом another, сопоставляется с этим AnotherComponent бинсом аналогично предыдущему примеру SomeProperties.

2.8.6. Расслабленная привязка

Spring Boot использует некоторые правила гибкой привязки свойств Environment к объектам @ConfigurationProperties, поэтому точное совпадение имени свойства Environment и имени свойства объекта не требуется. Типичные примеры, где это полезно, включают свойства среды, разделенные дефисом (например, context-path привязывается к contextPath) и свойства среды с заглавными буквами (например, PORT привязывается к port).

В качестве примера рассмотрим следующий класс @ConfigurationProperties:

Java
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "my.main-project.person")
public class MyPersonProperties {

    private String firstName;

    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties(prefix = "my.main-project.person")
class MyPersonProperties {

    var firstName: String? = null

}

С указанным кодом могут использоваться следующие имена свойств:

Таблица 3. Расслабленная привязка
Свойство Примечание

my.main-project.person.first-name

Кебаб-кейс, который рекомендуется использовать в файлах .properties и YAML.

my.main-project.person.firstName

Стандартный синтаксис camelCase.

my.main-project.person.first_name

Символы подчёркивания, это альтернативный формат для использования в файлах .properties и YAML.

MY_MAINPROJECT_PERSON_FIRSTNAME

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

Значение prefix для аннотации должно быть в формате kebab case (строчные буквы и разделены -, например, my.main-project.person).
Таблица 4. Правила расслабленной привязки по источникам свойств
Источник свойства Простой Список

Файлы свойств

Camel case, kebab case или с символами подчёркивания

Стандартный синтаксис списка, использующий [ ] или значения, разделённые запятыми

Файлы YAML

Camel case, kebab case или с символами подчёркивания

Стандартный синтаксис списка YAML или значения, разделённые запятыми

Переменные среды

Формат с заглавными буквами с символом подчёркивания в качестве разделителя (см. Привязка из переменных среды).

Числовые значения, окружённые символами подчёркивания (см. Привязка из переменных среды)

Свойства системы

Camel case, kebab case или с символами подчёркивания

Стандартный синтаксис списка, использующий [ ] или значения, разделённые запятыми

Рекомендуется, если это возможно, хранить свойства в формате lower-case kebab, например, my.person.first-name=Rod.
Привязка словарей

При привязке к Map свойствам может потребоваться использовать специальный синтаксис с квадратными скобками, чтобы сохранить исходное значение key. Если ключ не окружён [], любые символы, не являющиеся буквенно-цифровыми, - или ., будут удалены.

Например, рассмотрим привязку следующих свойств к Map<String,String>:

Свойства
my.map.[/key1]=value1
my.map.[/key2]=value2
my.map./key3=value3
Yaml
my:
  map:
    "[/key1]": "value1"
    "[/key2]": "value2"
    "/key3": "value3"
Для файлов YAML скобки должны быть заключены в кавычки, чтобы ключи были правильно обработаны.

Вышеперечисленные свойства будут привязаны к Map со значениями /key1, /key2 и key3 в качестве ключей словаря. Слэш был удалён из key3, так как он не был заключён в квадратные скобки.

При привязке к скалярным значениям, ключи, содержащие ., не нуждаются в окружении []. Скалярные значения включают перечисления и все типы в пакете java.lang за исключением Object. Привязка a.b=c к Map<String, String> сохранит . в ключе и вернёт словарь со значением {"a.b"="c"}. Для других типов необходимо использовать синтаксис с квадратными скобками, если ваше key содержит .. Например, привязка a.b=c к Map<String, Object> вернёт словарь со значением {"a"={"b"="c"}}, тогда как [a.b]=c вернёт словарь со значением {"a.b"="c"}.

Привязка из переменных среды

Большинство операционных систем накладывают жёсткие ограничения на имена, которые могут использоваться для переменных среды. Например, переменные оболочки Linux могут содержать только буквы (a до z или A до Z), цифры (0 до 9) или символ подчёркивания (_). По соглашению, имена переменных оболочки Unix также будут в верхнем регистре.

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

Чтобы преобразовать имя свойства в канонической форме в имя переменной среды, можно следовать этим правилам:

  • Замените точки (.) символами подчёркивания (_).

  • Удалите все дефисы (-).

  • Преобразуйте в верхний регистр.

Например, свойство конфигурации spring.main.log-startup-info будет переменной среды, названной SPRING_MAIN_LOGSTARTUPINFO.

Переменные среды также могут быть использованы при привязке к спискам объектов. Чтобы привязаться к элементу List, номер элемента должен быть заключен в символы подчёркивания в имени переменной.

Например, свойство конфигурации my.service[0].other будет использовать переменную среды, названную MY_SERVICE_0_OTHER.

2.8.7. Объединение сложных типов

При конфигурировании списков в нескольких местах перезапись происходит заменой всего списка.

Например, предположим объект MyPojo с атрибутами name и description, которые по умолчанию являются null. Следующий пример демонстрирует список объектов MyPojo из MyProperties:

Java
import java.util.ArrayList;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("my")
public class MyProperties {

    private final List<MyPojo> list = new ArrayList<>();

    public List<MyPojo> getList() {
        return this.list;
    }

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties("my")
class MyProperties {

    val list: List<MyPojo> = ArrayList()

}

Рассмотрим следующую конфигурацию:

Properties
my.list[0].name=my name
my.list[0].description=my description
#---
spring.config.activate.on-profile=dev
my.list[0].name=my another name
Yaml
my:
  list:
  - name: "my name"
    description: "my description"
---
spring:
  config:
    activate:
      on-profile: "dev"
my:
  list:
  - name: "my another name"

Если профиль dev не активен, MyProperties.list содержит одну запись MyPojo, как определено ранее. Однако, если профиль dev активирован, list по-прежнему содержит только одну запись (с именем my another name и описанием null). Эта конфигурация не добавляет второй экземпляр MyPojo в список и не объединяет элементы.

Когда List указан в нескольких профилях, используется только тот, у которого приоритет выше. Рассмотрим следующий пример:

Properties
my.list[0].name=my name
my.list[0].description=my description
my.list[1].name=another name
my.list[1].description=another description
#---
spring.config.activate.on-profile=dev
my.list[0].name=my another name
Yaml
my:
  list:
  - name: "my name"
    description: "my description"
  - name: "another name"
    description: "another description"
---
spring:
  config:
    activate:
      on-profile: "dev"
my:
  list:
  - name: "my another name"

В предыдущем примере, если профиль dev активен, MyProperties.list содержит одну запись MyPojo (с именем my another name и описанием null). Для YAML можно использовать как списки, разделенные запятыми, так и списки YAML для полной перезаписи содержимого списка.

Для свойств Map можно привязываться к значениям свойств из нескольких источников. Однако для одного и того же свойства в нескольких источниках используется тот, у которого приоритет выше. Следующий пример демонстрирует Map<String, MyPojo> из MyProperties:

Java
import java.util.LinkedHashMap;
import java.util.Map;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("my")
public class MyProperties {

    private final Map<String, MyPojo> map = new LinkedHashMap<>();

    public Map<String, MyPojo> getMap() {
        return this.map;
    }

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties("my")
class MyProperties {

    val map: Map<String, MyPojo> = LinkedHashMap()

}

Рассмотрим следующую конфигурацию:

Properties
my.map.key1.name=my name 1
my.map.key1.description=my description 1
#---
spring.config.activate.on-profile=dev
my.map.key1.name=dev name 1
my.map.key2.name=dev name 2
my.map.key2.description=dev description 2
Yaml
my:
  map:
    key1:
      name: "my name 1"
      description: "my description 1"
---
spring:
  config:
    activate:
      on-profile: "dev"
my:
  map:
    key1:
      name: "dev name 1"
    key2:
      name: "dev name 2"
      description: "dev description 2"

Если профиль dev не активен, MyProperties.map содержит одну запись с ключом key1 (с именем my name 1 и описанием my description 1). Однако, если профиль dev включен, map содержит две записи с ключами key1 (с именем dev name 1 и описанием my description 1) и key2 (с именем dev name 2 и описанием dev description 2).

Предыдущие правила объединения применяются к свойствам из всех источников свойств, а не только к файлам.

2.8.8. Преобразование свойств

Spring Boot пытается привести внешние свойства приложения к нужному типу при привязке к @ConfigurationProperties биндам. Если вам нужна настройка преобразования типов, вы можете предоставить ConversionService бин (с именем бина conversionService) или пользовательские редакторы свойств (через CustomEditorConfigurer бин) или пользовательские Converters (с определениями бинов, помеченных как @ConfigurationPropertiesBinding).

Поскольку этот бин запрашивается очень рано на жизненном цикле приложения, убедитесь, что зависимости вашего ConversionService ограничены. Как правило, любая необходимая вам зависимость может быть не полностью инициализирована во время создания. Вы можете переименовать свой пользовательский ConversionService, если он не требуется для преобразования ключей конфигурации, и полагаться только на пользовательских преобразователях, квалифицированных как @ConfigurationPropertiesBinding.
Преобразование продолжительностей

Spring Boot имеет специальную поддержку для выражения продолжительностей. Если вы экспонируете свойство java.time.Duration, доступны следующие форматы в свойствах приложения:

  • Обычное представление long (с использованием миллисекунд в качестве единицы по умолчанию, если не указана другая единица @DurationUnit)

  • Стандартный формат ISO-8601 используемый java.time.Duration

  • Более читаемый формат, где значение и единица объединены (10s означает 10 секунд)

Рассмотрим следующий пример:

Java
import java.time.Duration;
import java.time.temporal.ChronoUnit;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;

@ConfigurationProperties("my")
public class MyProperties {

    @DurationUnit(ChronoUnit.SECONDS)
    private Duration sessionTimeout = Duration.ofSeconds(30);

    private Duration readTimeout = Duration.ofMillis(1000);

    // getters / setters...

    public Duration getSessionTimeout() {
        return this.sessionTimeout;
    }

    public void setSessionTimeout(Duration sessionTimeout) {
        this.sessionTimeout = sessionTimeout;
    }

    public Duration getReadTimeout() {
        return this.readTimeout;
    }

    public void setReadTimeout(Duration readTimeout) {
        this.readTimeout = readTimeout;
    }

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.convert.DurationUnit
import java.time.Duration
import java.time.temporal.ChronoUnit

@ConfigurationProperties("my")
class MyProperties {

    @DurationUnit(ChronoUnit.SECONDS)
    var sessionTimeout = Duration.ofSeconds(30)

    var readTimeout = Duration.ofMillis(1000)

}

Для указания таймаута сессии в 30 секунд, 30, PT30S и 30s все эквивалентны. Таймаут чтения в 500 мс может быть задан в любом из следующих форматов: 500, PT0.5S и 500ms.

Вы также можете использовать любые поддерживаемые единицы. Вот они:

  • ns для наносекунд

  • us для микросекунд

  • ms для миллисекунд

  • s для секунд

  • m для минут

  • h для часов

  • d для дней

Единицей по умолчанию является миллисекунда, и её можно переопределить, используя @DurationUnit, как показано в примере выше.

Если вы предпочитаете использовать привязку к конструктору, те же свойства можно экспонировать, как показано в следующем примере:

Java
import java.time.Duration;
import java.time.temporal.ChronoUnit;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.boot.convert.DurationUnit;

@ConfigurationProperties("my")
public class MyProperties {

    // fields...

    private final Duration sessionTimeout;

    private final Duration readTimeout;

    public MyProperties(@DurationUnit(ChronoUnit.SECONDS) @DefaultValue("30s") Duration sessionTimeout,
            @DefaultValue("1000ms") Duration readTimeout) {
        this.sessionTimeout = sessionTimeout;
        this.readTimeout = readTimeout;
    }

    // getters...

    public Duration getSessionTimeout() {
        return this.sessionTimeout;
    }

    public Duration getReadTimeout() {
        return this.readTimeout;
    }

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.DefaultValue
import org.springframework.boot.convert.DurationUnit
import java.time.Duration
import java.time.temporal.ChronoUnit

@ConfigurationProperties("my")
class MyProperties(@param:DurationUnit(ChronoUnit.SECONDS) @param:DefaultValue("30s") val sessionTimeout: Duration,
        @param:DefaultValue("1000ms") val readTimeout: Duration)
Если вы обновляете свойство Long, убедитесь, что вы определили единицу (используя @DurationUnit), если это не миллисекунды. Это даёт прозрачный путь обновления, поддерживая более богатый формат.
Преобразование периодов

Помимо продолжительностей, Spring Boot также может работать с типом java.time.Period. В свойствах приложения можно использовать следующие форматы:

  • Обычное представление int (с использованием дней в качестве единицы по умолчанию, если не указана другая единица @PeriodUnit)

  • Стандартный формат ISO-8601 используемый java.time.Period

  • Более простой формат, где значение и единица пар связаны (1y3d означает 1 год и 3 дня)

Следующие единицы поддерживаются в простом формате:

  • y для лет

  • m для месяцев

  • w для недель

  • d для дней

Тип java.time.Period никогда не хранит количество недель, это сокращение, означающее «7 дней».
Преобразование размеров данных

Spring Framework имеет тип значений DataSize, который выражает размер в байтах. Если вы экспонируете свойство DataSize, доступны следующие форматы в свойствах приложения:

  • Обычное представление long (с использованием байтов в качестве единицы по умолчанию, если не указана другая единица @DataSizeUnit)

  • Более читаемый формат, где значение и единица пар связаны (10MB означает 10 мегабайт)

Рассмотрим следующий пример:

Java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DataSizeUnit;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;

@ConfigurationProperties("my")
public class MyProperties {

    @DataSizeUnit(DataUnit.MEGABYTES)
    private DataSize bufferSize = DataSize.ofMegabytes(2);

    private DataSize sizeThreshold = DataSize.ofBytes(512);

    // getters/setters...

    public DataSize getBufferSize() {
        return this.bufferSize;
    }

    public void setBufferSize(DataSize bufferSize) {
        this.bufferSize = bufferSize;
    }

    public DataSize getSizeThreshold() {
        return this.sizeThreshold;
    }

    public void setSizeThreshold(DataSize sizeThreshold) {
        this.sizeThreshold = sizeThreshold;
    }

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.convert.DataSizeUnit
import org.springframework.util.unit.DataSize
import org.springframework.util.unit.DataUnit

@ConfigurationProperties("my")
class MyProperties {

    @DataSizeUnit(DataUnit.MEGABYTES)
    var bufferSize = DataSize.ofMegabytes(2)

    var sizeThreshold = DataSize.ofBytes(512)

}

Для указания размера буфера в 10 мегабайт, 10 и 10MB эквивалентны. Порог размера в 256 байт можно указать как 256 или 256B.

Вы также можете использовать любые поддерживаемые единицы. Вот они:

  • B для байт

  • KB для килобайт

  • MB для мегабайт

  • GB для гигабайт

  • TB для терабайт

Единицей по умолчанию является байт, и её можно переопределить, используя @DataSizeUnit, как показано в примере выше.

Если вы предпочитаете использовать привязку к конструктору, те же свойства можно экспонировать, как показано в следующем примере:

Java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.boot.convert.DataSizeUnit;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;

@ConfigurationProperties("my")
public class MyProperties {

    // fields...

    private final DataSize bufferSize;

    private final DataSize sizeThreshold;

    public MyProperties(@DataSizeUnit(DataUnit.MEGABYTES) @DefaultValue("2MB") DataSize bufferSize,
            @DefaultValue("512B") DataSize sizeThreshold) {
        this.bufferSize = bufferSize;
        this.sizeThreshold = sizeThreshold;
    }

    // getters...

    public DataSize getBufferSize() {
        return this.bufferSize;
    }

    public DataSize getSizeThreshold() {
        return this.sizeThreshold;
    }

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.boot.context.properties.bind.DefaultValue
import org.springframework.boot.convert.DataSizeUnit
import org.springframework.util.unit.DataSize
import org.springframework.util.unit.DataUnit

@ConfigurationProperties("my")
class MyProperties(@param:DataSizeUnit(DataUnit.MEGABYTES) @param:DefaultValue("2MB") val bufferSize: DataSize,
        @param:DefaultValue("512B") val sizeThreshold: DataSize)
Если вы обновляете свойство Long, убедитесь, что определили единицу (используя @DataSizeUnit), если это не байты. Это даёт прозрачный путь обновления, поддерживая более богатый формат.

2.8.9. Валидация @ConfigurationProperties

Spring Boot пытается валидировать @ConfigurationProperties классы всякий раз, когда они помечены аннотацией Spring @Validated. Вы можете напрямую использовать аннотации ограничений JSR-303 jakarta.validation на вашем классе конфигурации. Для этого убедитесь, что соответствующая реализация JSR-303 находится в вашем классе пути, а затем добавьте аннотации ограничений к вашим полям, как показано в следующем примере:

Java
import java.net.InetAddress;

import jakarta.validation.constraints.NotNull;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

@ConfigurationProperties("my.service")
@Validated
public class MyProperties {

    @NotNull
    private InetAddress remoteAddress;

    // getters/setters...

    public InetAddress getRemoteAddress() {
        return this.remoteAddress;
    }

    public void setRemoteAddress(InetAddress remoteAddress) {
        this.remoteAddress = remoteAddress;
    }

}
Kotlin
import jakarta.validation.constraints.NotNull
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.validation.annotation.Validated
import java.net.InetAddress

@ConfigurationProperties("my.service")
@Validated
class MyProperties {

    var remoteAddress: @NotNull InetAddress? = null

}
Также можно инициировать валидацию, пометив метод @Bean, который создаёт свойства конфигурации, аннотацией @Validated.

Чтобы гарантировать, что валидация всегда срабатывает для вложенных свойств, даже если свойства не найдены, соответствующее поле должно быть помечено аннотацией @Valid. Следующий пример расширяет предыдущий пример MyProperties:

Java
import java.net.InetAddress;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

@ConfigurationProperties("my.service")
@Validated
public class MyProperties {

    @NotNull
    private InetAddress remoteAddress;

    @Valid
    private final Security security = new Security();

    // getters/setters...

    public InetAddress getRemoteAddress() {
        return this.remoteAddress;
    }

    public void setRemoteAddress(InetAddress remoteAddress) {
        this.remoteAddress = remoteAddress;
    }

    public Security getSecurity() {
        return this.security;
    }

    public static class Security {

        @NotEmpty
        private String username;

        // getters/setters...

        public String getUsername() {
            return this.username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

    }

}
Kotlin
import jakarta.validation.Valid
import jakarta.validation.constraints.NotEmpty
import jakarta.validation.constraints.NotNull
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.validation.annotation.Validated
import java.net.InetAddress

@ConfigurationProperties("my.service")
@Validated
class MyProperties {

    var remoteAddress: @NotNull InetAddress? = null

    @Valid
    val security = Security()

    class Security {

        @NotEmpty
        var username: String? = null

    }

}

Вы также можете добавить пользовательский Spring Validator, создав определение бина под названием configurationPropertiesValidator. Метод @Bean должен быть объявлен static. Валидатор свойств конфигурации создаётся очень рано на жизненном цикле приложения, а объявление метода @Bean как статического позволяет создать бин без необходимости инстанцирования класса @Configuration. Это предотвращает любые проблемы, которые могут возникнуть из-за ранней инстанциации.

Модуль spring-boot-actuator включает конечную точку, которая экспонирует все @ConfigurationProperties бины. Откройте в вашем веб-браузере /actuator/configprops или используйте эквивалентную конечную точку JMX. Подробности смотрите в разделе «Готовые к использованию в производственной среде функции».

2.8.10. @ConfigurationProperties против @Value

Аннотация @Value — это основная функция контейнера, и она не предоставляет те же возможности, что и типизированные свойства конфигурации. В следующей таблице обобщены возможности, поддерживаемые @ConfigurationProperties и @Value:

Функция @ConfigurationProperties @Value

Гибкая привязка

Да

Ограниченно (см. примечание ниже)

Поддержка метаданных

Да

Нет

SpEL вычисление

Нет

Да

Если вы хотите использовать @Value, рекомендуется использовать канонические имена свойств (кебаб-кейс, только строчные буквы). Это позволит Spring Boot использовать ту же логику, что и при гибкой привязке @ConfigurationProperties.

Например, @Value("${demo.item-price}") будет подбирать demo.item-price и demo.itemPrice формы из файла application.properties, а также DEMO_ITEMPRICE из системной среды. Если бы вы использовали @Value("${demo.itemPrice}"), то demo.item-price и DEMO_ITEMPRICE не учитывались бы.

Если вы определяете набор ключей конфигурации для собственных компонентов, рекомендуется сгруппировать их в POJO, аннотированном с помощью @ConfigurationProperties. Это обеспечит вам структурированный, типизированный объект, который можно ввести в свои собственные бины.

SpEL выражения из файлов свойств приложения не обрабатываются во время разбора этих файлов и заполнения среды. Однако можно записать выражение SpEL в @Value. Если значение свойства из файла свойств приложения является выражением SpEL, оно будет вычислено при использовании через @Value.

3. Профили

Профили Spring позволяют разделить части конфигурации приложения и сделать их доступными только в определенных средах. Любой @Component, @Configuration или @ConfigurationProperties может быть помечен аннотацией @Profile, чтобы ограничить время загрузки, как показано в следующем примере:

Java
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration(proxyBeanMethods = false)
@Profile("production")
public class ProductionConfiguration {

    // ...

}
Kotlin
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile

@Configuration(proxyBeanMethods = false)
@Profile("production")
class ProductionConfiguration {

    // ...

}
Если бины @ConfigurationProperties регистрируются через @EnableConfigurationProperties вместо автоматической проверки, аннотация @Profile должна быть указана в классе @Configuration, имеющем аннотацию @EnableConfigurationProperties. В случае, если @ConfigurationProperties проверяются, @Profile может быть указана на самом классе @ConfigurationProperties.

Вы можете использовать свойство spring.profiles.active Environment для указания активных профилей. Вы можете указать свойство любым из способов, описанных ранее в этой главе. Например, вы можете включить его в свой application.properties, как показано в следующем примере:

Свойства
spring.profiles.active=dev,hsqldb
Yaml
spring:
  profiles:
    active: "dev,hsqldb"

Вы также можете указать его в командной строке, используя следующий переключатель: --spring.profiles.active=dev,hsqldb.

Если ни один профиль не активен, активируется по умолчанию. Имя профиля по умолчанию — default, и его можно настроить с помощью свойства spring.profiles.default Environment, как показано в следующем примере:

Свойства
spring.profiles.default=none
Yaml
spring:
  profiles:
    default: "none"

spring.profiles.active и spring.profiles.default могут использоваться только в документах, не связанных с профилями. Это означает, что их нельзя включать в файлы, специфичные для профилей, или в документы, активируемые spring.config.activate.on-profile.

Например, вторая конфигурация документа недопустима:

Свойства
# this document is valid
spring.profiles.active=prod
#---
# this document is invalid
spring.config.activate.on-profile=prod
spring.profiles.active=metrics
Yaml
# this document is valid
spring:
  profiles:
    active: "prod"
---
# this document is invalid
spring:
  config:
    activate:
      on-profile: "prod"
  profiles:
    active: "metrics"

3.1. Добавление активных профилей

Свойство spring.profiles.active подчиняется тем же правилам упорядочивания, что и другие свойства: побеждает самое высокое PropertySource. Это означает, что вы можете указать активные профили в application.properties, а затем заменить их, используя переключатель командной строки.

Иногда полезно иметь свойства, которые добавляют активные профили, а не заменяют их. Свойство spring.profiles.include можно использовать для добавления активных профилей поверх тех, которые активированы свойством spring.profiles.active. Точка входа SpringApplication также имеет API Java для установки дополнительных профилей. См. метод setAdditionalProfiles() в SpringApplication.

Например, при запуске приложения со следующими свойствами, профили common и local будут активированы, даже если приложение запускается с переключателем --spring.profiles.active:

Свойства
spring.profiles.include[0]=common
spring.profiles.include[1]=local
Yaml
spring:
  profiles:
    include:
      - "common"
      - "local"
Подобно spring.profiles.active, spring.profiles.include может использоваться только в документах, не связанных с профилями. Это означает, что его нельзя включать в файлы, специфичные для профилей, или в документы, активируемые spring.config.activate.on-profile.

Группы профилей, которые описаны в следующем разделе следующем разделе, также могут быть использованы для добавления активных профилей, если определенный профиль активен.

3.2. Группы профилей

Иногда определенные вами и используемые в приложении профили слишком детализированы и их использование становится громоздким. Например, у вас могут быть профили proddb и prodmq, которые вы используете для включения функций базы данных и обмена сообщениями независимо.

Для решения этой проблемы Spring Boot позволяет определять группы профилей. Группа профилей позволяет определить логическое имя для связанной группы профилей.

Например, мы можем создать группу production, которая состоит из наших профилей proddb и prodmq.

Свойства
spring.profiles.group.production[0]=proddb
spring.profiles.group.production[1]=prodmq
Yaml
spring:
  profiles:
    group:
      production:
      - "proddb"
      - "prodmq"

Теперь наше приложение можно запустить, используя --spring.profiles.active=production для активации профилей production, proddb и prodmq в один клик.

3.3. Программная установка профилей

Вы можете программно установить активные профили, вызвав SpringApplication.setAdditionalProfiles(…​) перед запуском приложения. Также возможно активировать профили, используя интерфейс Spring ConfigurableEnvironment.

3.4. Файлы конфигурации, специфичные для профилей

Файлы, представляющие собой варианты, специфичные для профилей, как для application.properties (или application.yaml), так и для файлов, ссылающихся через @ConfigurationProperties, рассматриваются как файлы и загружаются. Подробнее см. "Файлы, специфичные для профилей".

END_OF_DOCUMENT_MARKER

4. Ведение журнала

Spring Boot использует Commons Logging для всего внутреннего ведения журнала, но оставляет реализацию основного журнала открытой. Предоставляются конфигурации по умолчанию для Java Util Logging, Log4j2 и Logback. В каждом случае логгеры предварительно настроены для использования вывода на консоль, а также доступен необязательный вывод в файл.

По умолчанию, если вы используете «Стартеры», для ведения журнала используется Logback. Также включено соответствующее маршрутизирование Logback, чтобы убедиться, что зависимые библиотеки, использующие Java Util Logging, Commons Logging, Log4J или SLF4J, работают корректно.

Существует множество фреймворков ведения журнала для Java. Не беспокойтесь, если вышеприведенный список кажется запутанным. Как правило, вам не нужно изменять свои зависимости ведения журнала, и значения Spring Boot по умолчанию работают прекрасно.
При развертывании вашего приложения в контейнере Servlet или прикладном сервере, ведение журнала с помощью API Java Util Logging не маршрутизируется в журналы вашего приложения. Это предотвращает появление в журналах вашего приложения записей ведения журнала, выполненных контейнером или другими приложениями, развернутыми в нём.

4.1. Формат журнала

Вывод журнала по умолчанию Spring Boot напоминает следующий пример:

2023-08-24T09:32:40.656Z  INFO 34602 --- [           main] o.s.b.d.f.logexample.MyApplication       : Starting MyApplication using Java 17.0.8 with PID 34602 (/opt/apps/myapp.jar started by myuser in /opt/apps/)
2023-08-24T09:32:40.661Z  INFO 34602 --- [           main] o.s.b.d.f.logexample.MyApplication       : No active profile set, falling back to 1 default profile: "default"
2023-08-24T09:32:42.277Z  INFO 34602 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2023-08-24T09:32:42.292Z  INFO 34602 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2023-08-24T09:32:42.292Z  INFO 34602 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.12]
2023-08-24T09:32:42.423Z  INFO 34602 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2023-08-24T09:32:42.425Z  INFO 34602 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1698 ms
2023-08-24T09:32:42.940Z  INFO 34602 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2023-08-24T09:32:42.952Z  INFO 34602 --- [           main] o.s.b.d.f.logexample.MyApplication       : Started MyApplication in 2.93 seconds (process running for 3.354)

Выводится следующее:

  • Дата и время: с точностью до миллисекунд и легко сортируемые.

  • Уровень журнала: ERROR, WARN, INFO, DEBUG или TRACE.

  • Идентификатор процесса.

  • Разделитель ---, чтобы отличить начало фактических сообщений журнала.

  • Имя потока: заключено в квадратные скобки (может быть усечено для вывода на консоль).

  • Имя логгера: обычно это имя класса источника (часто сокращённое).

  • Сообщение журнала.

Logback не имеет уровня FATAL. Он сопоставлен с ERROR.

4.2. Вывод на консоль

Конфигурация журнала по умолчанию отражает сообщения на консоль по мере их написания. По умолчанию, сообщения уровня ERROR, WARN и INFO регистрируются. Вы также можете включить режим «отладки», запустив ваше приложение с флагом --debug.

$ java -jar myapp.jar --debug
Вы также можете указать debug=true в вашем application.properties.

Когда режим отладки включён, ряд ключевых логгеров (встроенный контейнер, Hibernate и Spring Boot) настроены на вывод дополнительной информации. Включение режима отладки не настраивает ваше приложение на запись всех сообщений с уровнем DEBUG.

В качестве альтернативы, вы можете включить режим «трассировки», запустив ваше приложение с флагом --trace (или trace=true в вашем application.properties). Это позволит включить трассировочный вывод журнала для ряда ключевых логгеров (встроенный контейнер, генерация схемы Hibernate и весь портфель Spring).

4.2.1. Цветной вывод

Если ваш терминал поддерживает ANSI, используется цветной вывод для повышения читаемости. Вы можете установить spring.output.ansi.enabled на поддерживаемое значение, чтобы переопределить автоматическое обнаружение.

Цветное кодирование настраивается с помощью ключевого слова преобразования %clr. В самом простом виде преобразователь окрашивает вывод в соответствии с уровнем журнала, как показано в следующем примере:

%clr(%5p)

В следующей таблице описывается сопоставление уровней журнала с цветами:

Уровень Цвет

FATAL

Красный

ERROR

Красный

WARN

Жёлтый

INFO

Зелёный

DEBUG

Зелёный

TRACE

Зелёный

В качестве альтернативы, вы можете указать цвет или стиль, которые должны быть использованы, указав их как опцию для преобразования. Например, чтобы сделать текст жёлтым, используйте следующее значение:

%clr(%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX}){yellow}

Следующие цвета и стили поддерживаются:

  • blue

  • cyan

  • faint

  • green

  • magenta

  • red

  • yellow

4.3. Вывод в файл

По умолчанию Spring Boot записывает журналы только в консоль и не создаёт файлы журналов. Если вы хотите записывать файлы журналов помимо вывода на консоль, вам нужно установить свойство logging.file.name или logging.file.path (например, в вашем application.properties).

В следующей таблице показано, как свойства logging.* могут использоваться вместе:

Таблица 5. Свойства ведения журнала
logging.file.name logging.file.path Пример Описание

(нет)

(нет)

Только вывод на консоль.

Конкретный файл

(нет)

my.log

Запись в указанный файл журнала. Имена могут быть полными путями или относительными по отношению к текущей директории.

(нет)

Конкретная директория

/var/log

Запись spring.log в указанную директорию. Имена могут быть полными путями или относительными по отношению к текущей директории.

Файлы журналов вращаются, когда достигают 10 МБ и, как и в случае с выводом на консоль, сообщения уровня ERROR, WARN и INFO регистрируются по умолчанию.

Свойства ведения журнала независимы от фактической инфраструктуры ведения журнала. В результате, конкретные ключи конфигурации (такие как logback.configurationFile для Logback) не управляются Spring Boot.

4.4. Вращение файлов журнала

Если вы используете Logback, можно точно настроить параметры вращения журнала, используя свой файл application.properties или application.yaml. Для всех других систем логгирования вам нужно будет настроить параметры вращения самостоятельно (например, если вы используете Log4j2, то вы можете добавить файл log4j2.xml или log4j2-spring.xml).

Поддерживаются следующие свойства политики вращения:

Имя Описание

logging.logback.rollingpolicy.file-name-pattern

Шаблон имени файла, используемый для создания архивов журналов.

logging.logback.rollingpolicy.clean-history-on-start

Производится ли очистка архивов журналов при запуске приложения.

logging.logback.rollingpolicy.max-file-size

Максимальный размер файла журнала перед его архивацией.

logging.logback.rollingpolicy.total-size-cap

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

logging.logback.rollingpolicy.max-history

Максимальное количество сохраняемых архивных файлов журналов (по умолчанию 7).

4.5. Уровни журналов

Все поддерживаемые системы логгирования могут иметь уровни логгеров, установленные в Spring Environment (например, в application.properties), используя logging.level.<logger-name>=<level>, где level — это TRACE, DEBUG, INFO, WARN, ERROR, FATAL или OFF. Логгер root можно настроить, используя logging.level.root.

Следующий пример показывает потенциальные параметры логгирования в application.properties:

Свойства
logging.level.root=warn
logging.level.org.springframework.web=debug
logging.level.org.hibernate=error
Yaml
logging:
  level:
    root: "warn"
    org.springframework.web: "debug"
    org.hibernate: "error"

Также можно установить уровни логгирования, используя переменные окружения. Например, LOGGING_LEVEL_ORG_SPRINGFRAMEWORK_WEB=DEBUG установит org.springframework.web в DEBUG.

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

4.6. Группы журналов

Часто бывает полезно объединять связанные логгеры, чтобы их можно было настраивать одновременно. Например, вы можете часто изменять уровни логгирования для всех логгеров, связанных с Tomcat, но вам сложно запомнить верхние пакеты.

Чтобы помочь в этом, Spring Boot позволяет определять группы логгирования в вашем Spring Environment. Например, вот как вы можете определить группу «tomcat», добавив ее в ваш файл application.properties:

Свойства
logging.group.tomcat=org.apache.catalina,org.apache.coyote,org.apache.tomcat
Yaml
logging:
  group:
    tomcat: "org.apache.catalina,org.apache.coyote,org.apache.tomcat"

После определения вы можете изменить уровень для всех логгеров в группе одной строкой:

Свойства
logging.level.tomcat=trace
Yaml
logging:
  level:
    tomcat: "trace"

Spring Boot включает следующие предопределённые группы логгирования, которые можно использовать «из коробки»:

Имя Логгеры

web

org.springframework.core.codec, org.springframework.http, org.springframework.web, org.springframework.boot.actuate.endpoint.web, org.springframework.boot.web.servlet.ServletContextInitializerBeans

sql

org.springframework.jdbc.core, org.hibernate.SQL, org.jooq.tools.LoggerListener

4.7. Использование хука завершения работы

Для освобождения ресурсов логгирования при завершении работы приложения предоставляется хук завершения работы, который будет запускать очистку системы логгирования при выходе JVM. Этот хук регистрируется автоматически, если ваше приложение не развернуто как файл war. Если у вашего приложения сложная иерархия контекстов, хук завершения работы может оказаться не достаточным. Если это так, отключите хук завершения работы и изучите варианты, предоставляемые непосредственно используемой системой логгирования. Например, Logback предлагает селекторы контекста, которые позволяют создавать каждый логгер в собственном контексте. Вы можете использовать свойство logging.register-shutdown-hook для отключения хука завершения работы. Установив значение в false, вы отключите регистрацию. Вы можете установить свойство в файле application.properties или application.yaml:

Свойства
logging.register-shutdown-hook=false
Yaml
logging:
  register-shutdown-hook: false

4.8. Настройка собственного ведения журнала

Различные системы ведения журналов могут быть активированы путем включения соответствующих библиотек в classpath, и их можно дополнительно настроить, предоставив соответствующий файл конфигурации в корне classpath или в местоположении, указанном следующим свойством Spring Environment: logging.config.

Вы можете принудительно заставить Spring Boot использовать определенную систему ведения журналов, используя системное свойство org.springframework.boot.logging.LoggingSystem. Значение должно быть полностью квалифицированным именем класса реализации LoggingSystem. Вы также можете полностью отключить конфигурацию ведения журналов Spring Boot, используя значение none.

Поскольку ведение журнала инициализируется до создания ApplicationContext, невозможно управлять ведением журнала из @PropertySources в файлах Spring @Configuration. Единственный способ изменить систему ведения журнала или полностью отключить ее — это использовать системные свойства.

В зависимости от вашей системы ведения журналов загружаются следующие файлы:

Система ведения журнала Настройка

Logback

logback-spring.xml, logback-spring.groovy, logback.xml или logback.groovy

Log4j2

log4j2-spring.xml или log4j2.xml

JDK (Java Util Logging)

logging.properties

Если возможно, мы рекомендуем использовать варианты -spring для конфигурации ведения журнала (например, logback-spring.xml вместо logback.xml). Если вы используете стандартные местоположения конфигурации, Spring не может полностью контролировать инициализацию журнала.
Известны проблемы с загрузкой классов в Java Util Logging, которые вызывают проблемы при запуске из «выполняемого jar». Мы рекомендуем избегать его при запуске из «выполняемого jar», если это вообще возможно.

Для упрощения настройки некоторые другие свойства передаются из Spring Environment в системные свойства. Это позволяет свойствам потребляться конфигурацией системы ведения журналов. Например, установка logging.file.name в application.properties или LOGGING_FILE_NAME в качестве переменной среды приведет к установке системного свойства LOG_FILE. Передаваемые свойства описаны в следующей таблице:

Среда Spring Системное свойство Комментарии

logging.exception-conversion-word

LOG_EXCEPTION_CONVERSION_WORD

Слово преобразования, используемое при ведении журнала исключений.

logging.file.name

LOG_FILE

Если определено, используется в конфигурации журнала по умолчанию.

logging.file.path

LOG_PATH

Если определено, используется в конфигурации журнала по умолчанию.

logging.pattern.console

CONSOLE_LOG_PATTERN

Шаблон журнала для использования на консоли (stdout).

logging.pattern.dateformat

LOG_DATEFORMAT_PATTERN

Шаблон приложения для формата даты журнала.

logging.charset.console

CONSOLE_LOG_CHARSET

Кодировка для использования при ведении журнала на консоль.

logging.threshold.console

CONSOLE_LOG_THRESHOLD

Пороговый уровень журнала для использования при ведении журнала на консоль.

logging.pattern.file

FILE_LOG_PATTERN

Шаблон журнала для использования в файле (если включен LOG_FILE).

logging.charset.file

FILE_LOG_CHARSET

Кодировка для использования при ведении журнала в файл (если включен LOG_FILE).

logging.threshold.file

FILE_LOG_THRESHOLD

Пороговый уровень журнала для использования при ведении журнала в файл.

logging.pattern.level

LOG_LEVEL_PATTERN

Формат для отображения уровня журнала (по умолчанию %5p).

PID

PID

Текущий идентификатор процесса (обнаруженный, если возможно, и если он еще не определен как переменная среды ОС).

Если вы используете Logback, то также передаются следующие свойства:

Среда Spring Системное свойство Комментарии

logging.logback.rollingpolicy.file-name-pattern

LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN

Шаблон для имен файлов журналов с переадресацией (по умолчанию ${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz).

logging.logback.rollingpolicy.clean-history-on-start

LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START

Очищать ли архивные файлы журналов при запуске.

logging.logback.rollingpolicy.max-file-size

LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE

Максимальный размер файла журнала.

logging.logback.rollingpolicy.total-size-cap

LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP

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

logging.logback.rollingpolicy.max-history

LOGBACK_ROLLINGPOLICY_MAX_HISTORY

Максимальное количество архивных файлов журналов для хранения.

Все поддерживаемые системы ведения журналов могут обращаться к системным свойствам при анализе своих файлов конфигурации. См. примеры конфигураций по умолчанию в spring-boot.jar:

  • Logback

  • Log4j 2

  • Java Util logging

Если вы хотите использовать заполнитель в свойстве регистрации, вы должны использовать синтаксис Spring Boot, а не синтаксис базового фреймворка. Обратите внимание, что если вы используете Logback, вы должны использовать : в качестве разделителя между именем свойства и его значением по умолчанию, а не :-.

Вы можете добавить MDC и другие произвольные данные к строкам логов, переопределив только LOG_LEVEL_PATTERN (или logging.pattern.level с Logback). Например, если вы используете logging.pattern.level=user:%X{user} %5p, то формат лога по умолчанию содержит запись MDC для "user", если она существует, как показано в следующем примере.

2019-08-30 12:30:04.031 user:someone INFO 22174 --- [  nio-8080-exec-0] demo.Controller
Handling authenticated request

4.9. Расширения Logback

Spring Boot включает в себя ряд расширений Logback, которые могут помочь с расширенной настройкой. Вы можете использовать эти расширения в вашем файле конфигурации logback-spring.xml.

Поскольку стандартный файл конфигурации logback.xml загружается слишком рано, вы не можете использовать расширения в нём. Вам необходимо либо использовать logback-spring.xml, либо определить свойство logging.config.
Расширения не могут использоваться с функцией сканирования конфигурации Logback configuration scanning. Если вы попытаетесь это сделать, изменения в файле конфигурации приведут к ошибке, подобной одной из следующих, которая будет записана в лог:
ERROR in ch.qos.logback.core.joran.spi.Interpreter@4:71 - no applicable action for [springProperty], current ElementPath is [[configuration][springProperty]]
ERROR in ch.qos.logback.core.joran.spi.Interpreter@4:71 - no applicable action for [springProfile], current ElementPath is [[configuration][springProfile]]

4.9.1. Конфигурация, специфичная для профилей

Тэг <springProfile> позволяет по желанию включать или исключать разделы конфигурации в зависимости от активных профилей Spring. Разделы профилей поддерживаются в любом месте внутри элемента <configuration>. Используйте атрибут name для указания профиля, который принимает конфигурацию. Тэг <springProfile> может содержать имя профиля (например, staging) или выражение профиля. Выражение профиля позволяет выражать более сложную логику профилей, например production & (eu-central | eu-west). Более подробную информацию см. в руководстве по Spring Framework.

В следующем списке показаны три образца профилей:

<springProfile name="staging">
    <!-- configuration to be enabled when the "staging" profile is active -->
</springProfile>

<springProfile name="dev | staging">
    <!-- configuration to be enabled when the "dev" or "staging" profiles are active -->
</springProfile>

<springProfile name="!production">
    <!-- configuration to be enabled when the "production" profile is not active -->
</springProfile>

4.9.2. Свойства среды

Тэг <springProperty> позволяет экспонировать свойства из Spring Environment для использования в Logback. Это может быть полезно, если вы хотите получить доступ к значениям из вашего файла application.properties в вашей конфигурации Logback. Тэг работает аналогично стандартному тегу Logback <property>. Однако вместо указания непосредственного value, вы указываете source свойства (из Environment). Если вам нужно сохранить свойство в другом месте, кроме области local, вы можете использовать атрибут scope. Если вам нужно значение по умолчанию (на случай, если свойство не задано в Environment), вы можете использовать атрибут defaultValue. Следующий пример показывает, как экспонировать свойства для использования в Logback:

<springProperty scope="context" name="fluentHost" source="myapp.fluentd.host"
        defaultValue="localhost"/>
<appender name="FLUENT" class="ch.qos.logback.more.appenders.DataFluentAppender">
    <remoteHost>${fluentHost}</remoteHost>
    ...
</appender>
Имя свойства source должно быть указано в формате kebab-case (например, my.property-name). Однако свойства могут быть добавлены в Environment, используя более гибкие правила.

4.10. Расширения Log4j2

Spring Boot включает в себя ряд расширений Log4j2, которые могут помочь с расширенной настройкой. Вы можете использовать эти расширения в любом файле конфигурации log4j2-spring.xml.

Поскольку стандартный файл конфигурации log4j2.xml загружается слишком рано, вы не можете использовать расширения в нём. Вам необходимо либо использовать log4j2-spring.xml, либо определить свойство logging.config.
Расширения заменяют поддержку Spring Boot, предоставляемую Log4j. Вам следует убедиться, что вы не включаете модуль org.apache.logging.log4j:log4j-spring-boot в свой проект сборки.

4.10.1. Конфигурация, специфичная для профилей

Тэг <SpringProfile> позволяет по желанию включать или исключать разделы конфигурации в зависимости от активных профилей Spring. Разделы профилей поддерживаются в любом месте внутри элемента <Configuration>. Используйте атрибут name для указания профиля, который принимает конфигурацию. Тэг <SpringProfile> может содержать имя профиля (например, staging) или выражение профиля. Выражение профиля позволяет выражать более сложную логику профилей, например production & (eu-central | eu-west). Более подробную информацию см. в руководстве по Spring Framework.

В следующем списке показаны три образца профилей:

<SpringProfile name="staging">
    <!-- configuration to be enabled when the "staging" profile is active -->
</SpringProfile>

<SpringProfile name="dev | staging">
    <!-- configuration to be enabled when the "dev" or "staging" profiles are active -->
</SpringProfile>

<SpringProfile name="!production">
    <!-- configuration to be enabled when the "production" profile is not active -->
</SpringProfile>

4.10.2. Поиск свойств среды

Если вы хотите использовать свойства из вашего Spring Environment в конфигурации Log4j2, вы можете использовать префикс spring: для поиска свойств. Это может быть полезно, если вы хотите получить доступ к значениям из вашего файла application.properties в вашей конфигурации Log4j2.

Следующий пример показывает, как установить свойство Log4j2 под названием applicationName, которое считывает значение spring.application.name из Spring Environment:

<Properties>
    <Property name="applicationName">${spring:spring.application.name}</Property>
</Properties>
Ключ для поиска должен быть указан в формате kebab-case (например, my.property-name).

4.10.3. Системные свойства Log4j2

Log4j2 поддерживает ряд системных свойств, которые можно использовать для настройки различных элементов. Например, системное свойство log4j2.skipJansi можно использовать для настройки, будет ли ConsoleAppender пытаться использовать поток вывода Jansi в Windows.

Все системные свойства, загруженные после инициализации Log4j2, можно получить из Spring Environment. Например, вы можете добавить log4j2.skipJansi=false в свой файл application.properties, чтобы ConsoleAppender использовал Jansi в Windows.

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

5. Межкультурный обмен

Spring Boot поддерживает локализованные сообщения, чтобы ваше приложение могло обслуживать пользователей с разными языковыми предпочтениями. По умолчанию Spring Boot ищет наличие ресурсного пакета messages в корне пути к классам.

Автоконфигурация применяется, когда файл свойств по умолчанию для настроенного ресурсного пакета доступен (messages.properties по умолчанию). Если ваш ресурсный пакет содержит только языковые файлы свойств, вам необходимо добавить файл по умолчанию. Если не будет найдено ни одного файла свойств, соответствующего любому из настроенных базовых имён, MessageSource не будет сконфигурирован автоматически.

Базовое имя ресурсного пакета, а также несколько других атрибутов можно настроить, используя пространство имён spring.messages, как показано в следующем примере:

Свойства
spring.messages.basename=messages,config.i18n.messages
spring.messages.fallback-to-system-locale=false
Yaml
spring:
  messages:
    basename: "messages,config.i18n.messages"
    fallback-to-system-locale: false
spring.messages.basename поддерживает список местоположений, разделённых запятыми, либо квалификатор пакета, либо ресурс, разрешённый из корневого пути к классам.

См. MessageSourceProperties для получения дополнительной информации о поддерживаемых опциях.

6. JSON

Spring Boot предоставляет интеграцию с тремя библиотеками для работы с JSON:

  • Gson

  • Jackson

  • JSON-B

Jackson является предпочтительной и по умолчанию используемой библиотекой.

6.1. Jackson

Автоконфигурирование для Jackson предоставлено, и Jackson входит в состав spring-boot-starter-json. При наличии Jackson в классе путей, автоматически настраивается бин ObjectMapper. Для настройки конфигурации ObjectMapper предоставляются несколько свойств конфигурации .

6.1.1. Пользовательские сериализаторы и десериализаторы

Если вы используете Jackson для сериализации и десериализации данных JSON, вам может потребоваться написать собственные классы JsonSerializer и JsonDeserializer. Пользовательские сериализаторы обычно регистрируются в Jackson через модуль, но Spring Boot предоставляет альтернативную аннотацию @JsonComponent, которая упрощает непосредственную регистрацию Spring Beans.

Вы можете использовать аннотацию @JsonComponent непосредственно на реализациях JsonSerializer, JsonDeserializer или KeyDeserializer. Вы также можете использовать её для классов, содержащих сериализаторы/десериализаторы, как внутренние классы, как показано в следующем примере:

Java
import java.io.IOException;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import org.springframework.boot.jackson.JsonComponent;

@JsonComponent
public class MyJsonComponent {

    public static class Serializer extends JsonSerializer<MyObject> {

        @Override
        public void serialize(MyObject value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
            jgen.writeStartObject();
            jgen.writeStringField("name", value.getName());
            jgen.writeNumberField("age", value.getAge());
            jgen.writeEndObject();
        }

    }

    public static class Deserializer extends JsonDeserializer<MyObject> {

        @Override
        public MyObject deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException {
            ObjectCodec codec = jsonParser.getCodec();
            JsonNode tree = codec.readTree(jsonParser);
            String name = tree.get("name").textValue();
            int age = tree.get("age").intValue();
            return new MyObject(name, age);
        }

    }

}
Kotlin
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.SerializerProvider
import org.springframework.boot.jackson.JsonComponent
import java.io.IOException

@JsonComponent
class MyJsonComponent {

    class Serializer : JsonSerializer<MyObject>() {
        @Throws(IOException::class)
        override fun serialize(value: MyObject, jgen: JsonGenerator, serializers: SerializerProvider) {
            jgen.writeStartObject()
            jgen.writeStringField("name", value.name)
            jgen.writeNumberField("age", value.age)
            jgen.writeEndObject()
        }
    }

    class Deserializer : JsonDeserializer<MyObject>() {
        @Throws(IOException::class, JsonProcessingException::class)
        override fun deserialize(jsonParser: JsonParser, ctxt: DeserializationContext): MyObject {
            val codec = jsonParser.codec
            val tree = codec.readTree<JsonNode>(jsonParser)
            val name = tree["name"].textValue()
            val age = tree["age"].intValue()
            return MyObject(name, age)
        }
    }

}

Все @JsonComponent бины в ApplicationContext автоматически регистрируются в Jackson. Поскольку @JsonComponent мета-аннотирована @Component, применяются обычные правила сканирования компонентов.

Spring Boot также предоставляет JsonObjectSerializer и JsonObjectDeserializer базовые классы, которые предоставляют полезные альтернативы стандартным версиям Jackson при сериализации объектов. Подробнее см. JsonObjectSerializer и JsonObjectDeserializer в Javadoc.

Приведённый выше пример можно переписать, используя JsonObjectSerializer/JsonObjectDeserializer следующим образом:

Java
import java.io.IOException;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializerProvider;

import org.springframework.boot.jackson.JsonComponent;
import org.springframework.boot.jackson.JsonObjectDeserializer;
import org.springframework.boot.jackson.JsonObjectSerializer;

@JsonComponent
public class MyJsonComponent {

    public static class Serializer extends JsonObjectSerializer<MyObject> {

        @Override
        protected void serializeObject(MyObject value, JsonGenerator jgen, SerializerProvider provider)
                throws IOException {
            jgen.writeStringField("name", value.getName());
            jgen.writeNumberField("age", value.getAge());
        }

    }

    public static class Deserializer extends JsonObjectDeserializer<MyObject> {

        @Override
        protected MyObject deserializeObject(JsonParser jsonParser, DeserializationContext context, ObjectCodec codec,
                JsonNode tree) throws IOException {
            String name = nullSafeValue(tree.get("name"), String.class);
            int age = nullSafeValue(tree.get("age"), Integer.class);
            return new MyObject(name, age);
        }

    }

}
Kotlin
`object`

import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.ObjectCodec
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.SerializerProvider
import org.springframework.boot.jackson.JsonComponent
import org.springframework.boot.jackson.JsonObjectDeserializer
import org.springframework.boot.jackson.JsonObjectSerializer
import java.io.IOException

@JsonComponent
class MyJsonComponent {

    class Serializer : JsonObjectSerializer<MyObject>() {
        @Throws(IOException::class)
        override fun serializeObject(value: MyObject, jgen: JsonGenerator, provider: SerializerProvider) {
            jgen.writeStringField("name", value.name)
            jgen.writeNumberField("age", value.age)
        }
    }

    class Deserializer : JsonObjectDeserializer<MyObject>() {
        @Throws(IOException::class)
        override fun deserializeObject(jsonParser: JsonParser, context: DeserializationContext,
                codec: ObjectCodec, tree: JsonNode): MyObject {
            val name = nullSafeValue(tree["name"], String::class.java)
            val age = nullSafeValue(tree["age"], Int::class.java)
            return MyObject(name, age)
        }
    }

}

6.1.2. Mixins

Jackson поддерживает mixins, которые могут использоваться для добавления дополнительных аннотаций в уже объявленные на целевом классе. Автоконфигурация Jackson Spring Boot будет сканировать пакеты вашего приложения на наличие классов, аннотированных с помощью @JsonMixin, и регистрировать их с помощью автоматически настроенного ObjectMapper. Регистрация выполняется механизмом JsonMixinModule Spring Boot.

6.2. Gson

Автоконфигурация для Gson предоставляется. При наличии Gson в классе путей, автоматически настраивается бин Gson. Предоставляются несколько свойств конфигурации spring.gson.* для настройки конфигурации. Для большего контроля можно использовать один или несколько бинов GsonBuilderCustomizer.

6.3. JSON-B

Автоконфигурация для JSON-B предоставляется. При наличии API JSON-B и реализации в классе путей, бин Jsonb будет настроен автоматически. Предпочитаемой реализацией JSON-B является Eclipse Yasson, для которого обеспечивается управление зависимостями.

7. Выполнение и планирование задач

В отсутствии бинa Executor в контексте, Spring Boot автоматически настраивает ThreadPoolTaskExecutor с разумными значениями по умолчанию, которые могут быть автоматически связаны с асинхронным выполнением задач (@EnableAsync) и асинхронной обработкой запросов Spring MVC.

Если вы определили пользовательский Executor в контексте, обычное выполнение задач (то есть @EnableAsync) будет использовать его прозрачно, но поддержка Spring MVC не будет настроена, так как она требует реализации AsyncTaskExecutor (с именем applicationTaskExecutor). В зависимости от вашей целевой структуры, вы можете изменить ваш Executor на ThreadPoolTaskExecutor или определить как ThreadPoolTaskExecutor, так и AsyncConfigurer, обертывающие ваш пользовательский Executor.

Автоматически настроенный TaskExecutorBuilder позволяет легко создавать экземпляры, которые воспроизводят поведение автоконфигурации по умолчанию.

Потоковый пул использует 8 потоков ядра, которые могут увеличиваться и уменьшаться в соответствии с нагрузкой. Эти значения по умолчанию могут быть настроены с помощью пространства имён spring.task.execution, как показано в следующем примере:

Свойства
spring.task.execution.pool.max-size=16
spring.task.execution.pool.queue-capacity=100
spring.task.execution.pool.keep-alive=10s
Yaml
spring:
  task:
    execution:
      pool:
        max-size: 16
        queue-capacity: 100
        keep-alive: "10s"

Это изменяет пул потоков на использование ограниченной очереди, так что когда очередь заполняется (100 задач), пул потоков увеличивается до максимальных 16 потоков. Сжатие пула происходит более агрессивно, поскольку потоки изымаются, когда они простаивают в течение 10 секунд (вместо 60 секунд по умолчанию).

Также может быть автоматически настроен ThreadPoolTaskScheduler, если нужно его связать с выполнением запланированных задач (например, с помощью @EnableScheduling). Потоковый пул использует один поток по умолчанию, и его параметры можно настроить с помощью пространства имён spring.task.scheduling, как показано в следующем примере:

Свойства
spring.task.scheduling.thread-name-prefix=scheduling-
spring.task.scheduling.pool.size=2
Yaml
spring:
  task:
    scheduling:
      thread-name-prefix: "scheduling-"
      pool:
        size: 2

Если необходимо создать пользовательский исполняющий агент или планировщик, в контексте становятся доступными как бин TaskExecutorBuilder, так и бин TaskSchedulerBuilder.

8. Тестирование

Spring Boot предоставляет ряд утилит и аннотаций, которые помогут при тестировании вашего приложения. Поддержка тестирования обеспечивается двумя модулями: spring-boot-test содержит основные элементы, а spring-boot-test-autoconfigure поддерживает автоматическую настройку для тестов.

Большинство разработчиков используют spring-boot-starter-test «Starter», который импортирует как модули тестирования Spring Boot, так и JUnit Jupiter, AssertJ, Hamcrest и ряд других полезных библиотек.

Если у вас есть тесты, использующие JUnit 4, можно использовать винтажный движок JUnit 5 для их выполнения. Для использования винтажного движка добавьте зависимость от junit-vintage-engine, как показано в следующем примере:

<dependency>
    <groupId>org.junit.vintage</groupId>
    <artifactId>junit-vintage-engine</artifactId>
    <scope>test</scope>
    <exclusions>
        <exclusion>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>

hamcrest-core исключен в пользу org.hamcrest:hamcrest, который является частью spring-boot-starter-test.

8.1. Зависимости области тестирования

spring-boot-starter-test «Starter» (в test scope) содержит следующие предоставляемые библиотеки:

  • JUnit 5: Фактический стандарт для модульного тестирования Java-приложений.

  • Spring Test и Spring Boot Test: Утилиты и поддержка интеграционных тестов для Spring Boot-приложений.

  • AssertJ: Библиотека флюидных утверждений.

  • Hamcrest: Библиотека объектов-матчеров (также известных как ограничения или предикаты).

  • Mockito: Java-фреймворк для создания моков.

  • JSONassert: Библиотека утверждений для JSON.

  • JsonPath: XPath для JSON.

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

8.2. Тестирование Spring-приложений

Одним из основных преимуществ инъекции зависимостей является то, что это должно упростить тестирование вашего кода. Вы можете создавать экземпляры объектов, используя оператор new, даже не вовлекая Spring. Вы также можете использовать объекты-моки вместо реальных зависимостей.

Часто вам нужно выйти за рамки модульного тестирования и перейти к интеграционному тестированию (с Spring ApplicationContext). Полезно иметь возможность выполнять интеграционное тестирование без необходимости развертывания вашего приложения или подключения к другой инфраструктуре.

В Spring Framework есть специальный модуль для таких интеграционных тестов. Вы можете объявить зависимость непосредственно от org.springframework:spring-test или использовать spring-boot-starter-test «Starter», чтобы подключить его транзитивно.

Если вы раньше не использовали модуль spring-test, начните с прочтения соответствующего раздела документации по Spring Framework.

8.3. Тестирование приложений Spring Boot

Приложение Spring Boot является приложением Spring ApplicationContext, поэтому для его тестирования не требуется ничего особенного, кроме того, что обычно делается с обычным контекстом Spring.

Внешние свойства, логирование и другие функции Spring Boot устанавливаются в контексте по умолчанию только в том случае, если вы используете SpringApplication для его создания.

Spring Boot предоставляет аннотацию @SpringBootTest, которая может быть использована в качестве альтернативы стандартной аннотации spring-test @ContextConfiguration, когда требуются функции Spring Boot. Аннотация работает, создавая ApplicationContext, используемый в ваших тестах, через SpringApplication. В дополнение к @SpringBootTest предоставляется ряд других аннотаций для тестирования более специфических фрагментов приложения.

Если вы используете JUnit 4, не забудьте также добавить @RunWith(SpringRunner.class) в свой тест, иначе аннотации будут проигнорированы. Если вы используете JUnit 5, нет необходимости добавлять эквивалентную @ExtendWith(SpringExtension.class), поскольку @SpringBootTest и другие @…​Test аннотации уже аннотированы ею.

По умолчанию @SpringBootTest не будет запускать сервер. Вы можете использовать атрибут webEnvironment аннотации @SpringBootTest для дальнейшей настройки выполнения ваших тестов:

  • MOCK(По умолчанию): Загружает веб-ApplicationContext и предоставляет имитационную веб-среду. Встроенные серверы не запускаются при использовании этой аннотации. Если веб-среда недоступна в вашем классе, этот режим прозрачно переходит к созданию обычного не-веб-ApplicationContext. Его можно использовать совместно с @AutoConfigureMockMvc или @AutoConfigureWebTestClient для имитационного тестирования вашего веб-приложения.

  • RANDOM_PORT: Загружает WebServerApplicationContext и предоставляет реальную веб-среду. Встроенные серверы запускаются и слушают случайный порт.

  • DEFINED_PORT: Загружает WebServerApplicationContext и предоставляет реальную веб-среду. Встроенные серверы запускаются и слушают определенный порт (из вашего application.properties) или порт по умолчанию 8080.

  • NONE: Загружает ApplicationContext, используя SpringApplication, но не предоставляет никакую веб-среду (имитационную или иную).

Если ваш тест @Transactional, он отменяет транзакцию в конце каждого тестового метода по умолчанию. Однако, поскольку использование данного метода с RANDOM_PORT или DEFINED_PORT неявно предоставляет реальную среду сервлета, HTTP-клиент и сервер работают в отдельных потоках и, следовательно, в отдельных транзакциях. Любая транзакция, инициированная на сервере, в этом случае не отменяется.
@SpringBootTest с webEnvironment = WebEnvironment.RANDOM_PORT также запустит сервер управления на отдельном случайном порту, если ваше приложение использует другой порт для сервера управления.

8.3.1. Определение типа веб-приложения

Если Spring MVC доступен, настраивается обычный контекст приложения на основе MVC. Если у вас только Spring WebFlux, мы определим это и настроим контекст приложения на основе WebFlux вместо этого.

Если оба присутствуют, Spring MVC имеет приоритет. Если вы хотите протестировать реактивное веб-приложение в этом сценарии, необходимо установить свойство spring.main.web-application-type:

Java
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest(properties = "spring.main.web-application-type=reactive")
class MyWebFluxTests {

    // ...

}
Kotlin
import org.springframework.boot.test.context.SpringBootTest

@SpringBootTest(properties = ["spring.main.web-application-type=reactive"])
class MyWebFluxTests {

    // ...

}

8.3.2. Определение конфигурации теста

Если вы знакомы с Spring Test Framework, вы, возможно, привыкли использовать @ContextConfiguration(classes=…​) для указания, какой Spring @Configuration загружать. Или, возможно, вы часто использовали вложенные @Configuration классы в своих тестах.

При тестировании приложений Spring Boot это часто не требуется. Аннотации @*Test Spring Boot автоматически ищут основную конфигурацию, когда вы не определяете её явно.

Алгоритм поиска работает вверх с пакета, содержащего тест, до тех пор, пока не найдет класс, помеченный аннотациями @SpringBootApplication или @SpringBootConfiguration. При условии разумной структуры вашего кода, ваша основная конфигурация обычно находится.

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

Основная конфигурация сканирования компонентов @SpringBootApplication определяет фильтры исключения, которые используются для обеспечения корректной работы разделения. Если вы используете явную директиву @ComponentScan в вашем классе, помеченном @SpringBootApplication, помните, что эти фильтры будут отключены. Если вы используете разделение, вы должны снова определить их.

Если вы хотите настроить основную конфигурацию, вы можете использовать вложенный класс @TestConfiguration. В отличие от вложенного класса @Configuration, который бы использовался вместо основной конфигурации вашего приложения, вложенный класс @TestConfiguration используется дополнительно к основной конфигурации вашего приложения.

Тестовый фреймворк Spring кэширует контексты приложений между тестами. Поэтому, пока ваши тесты используют одну и ту же конфигурацию (неважно, как она обнаружена), потенциально ресурсоёмкий процесс загрузки контекста происходит только один раз.

8.3.3. Использование главного метода конфигурации теста

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

Например, ниже представлен распространённый шаблон кода для типичного приложения Spring Boot:

Java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }

}
Kotlin
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.docs.using.structuringyourcode.locatingthemainclass.MyApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class MyApplication

fun main(args: Array<String>) {
    runApplication<MyApplication>(*args)
}

В примере выше метод main не делает ничего, кроме делегирования вызова в SpringApplication.run. Однако возможно иметь более сложный метод main, который применяет настройки перед вызовом SpringApplication.run.

Например, вот приложение, которое изменяет режим баннера и задаёт дополнительные профили:

Java
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication application = new SpringApplication(MyApplication.class);
        application.setBannerMode(Banner.Mode.OFF);
        application.setAdditionalProfiles("myprofile");
        application.run(args);
    }

}
Kotlin
import org.springframework.boot.Banner
import org.springframework.boot.runApplication
import org.springframework.boot.autoconfigure.SpringBootApplication

@SpringBootApplication
class MyApplication

fun main(args: Array<String>) {
    runApplication<MyApplication>(*args) {
        setBannerMode(Banner.Mode.OFF)
        setAdditionalProfiles("myprofile");
    }
}

Поскольку настройки в методе main могут влиять на полученный ApplicationContext, возможно, вам также захочется использовать метод main для создания ApplicationContext, используемого в ваших тестах. По умолчанию, @SpringBootTest не будет вызывать ваш метод main, а вместо этого напрямую использует класс для создания ApplicationContext.

Если вы хотите изменить это поведение, вы можете изменить атрибут useMainMethod аннотации @SpringBootTest на UseMainMethod.ALWAYS или UseMainMethod.WHEN_AVAILABLE. При установке на ALWAYS тест завершится ошибкой, если не найден метод main. При установке на WHEN_AVAILABLE метод main будет использован, если он доступен, в противном случае будет использован стандартный механизм загрузки.

Например, следующий тест вызовет метод main класса MyApplication, чтобы создать ApplicationContext. Если главный метод устанавливает дополнительные профили, они будут активны при запуске ApplicationContext.

Java
import org.junit.jupiter.api.Test;

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.UseMainMethod;

@SpringBootTest(useMainMethod = UseMainMethod.ALWAYS)
class MyApplicationTests {

    @Test
    void exampleTest() {
        // ...
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.UseMainMethod
import org.springframework.context.annotation.Import

@SpringBootTest(useMainMethod = UseMainMethod.ALWAYS)
class MyApplicationTests {

    @Test
    fun exampleTest() {
        // ...
    }

}

8.3.4. Исключение тестовой конфигурации

Если ваше приложение использует сканирование компонентов (например, если вы используете @SpringBootApplication или @ComponentScan), вы можете случайно обнаружить классы конфигурации верхнего уровня, созданные только для определённых тестов, которые подхватываются везде.

Как мы уже видели, @TestConfiguration может быть использован для внутреннего класса теста для настройки основной конфигурации. При размещении на классе верхнего уровня, @TestConfiguration указывает, что классы в src/test/java не должны подхватываться сканированием. Тогда вы можете импортировать этот класс явно, где он необходим, как показано в следующем примере:

Java
import org.junit.jupiter.api.Test;

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;

@SpringBootTest
@Import(MyTestsConfiguration.class)
class MyTests {

    @Test
    void exampleTest() {
        // ...
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.context.annotation.Import

@SpringBootTest
@Import(MyTestsConfiguration::class)
class MyTests {

    @Test
    fun exampleTest() {
        // ...
    }

}
Если вы напрямую используете @ComponentScan (то есть, не через @SpringBootApplication), вам необходимо зарегистрировать TypeExcludeFilter с ним. Подробнее см. Javadoc.

8.3.5. Использование аргументов приложения

Если ваше приложение ожидает аргументы, вы можете использовать @SpringBootTest для их инъекции с помощью атрибута args.

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.test.context.SpringBootTest;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(args = "--app.test=one")
class MyApplicationArgumentTests {

    @Test
    void applicationArgumentsPopulated(@Autowired ApplicationArguments args) {
        assertThat(args.getOptionNames()).containsOnly("app.test");
        assertThat(args.getOptionValues("app.test")).containsOnly("one");
    }

}
Kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.ApplicationArguments
import org.springframework.boot.test.context.SpringBootTest

@SpringBootTest(args = ["--app.test=one"])
class MyApplicationArgumentTests {

    @Test
    fun applicationArgumentsPopulated(@Autowired args: ApplicationArguments) {
        assertThat(args.optionNames).containsOnly("app.test")
        assertThat(args.getOptionValues("app.test")).containsOnly("one")
    }

}

8.3.6. Тестирование с эмуляцией окружения

По умолчанию @SpringBootTest не запускает сервер, а вместо этого настраивает эмулированную среду для тестирования веб-точек входа.

С помощью Spring MVC, мы можем запросить наши веб-точки входа, используя MockMvc или WebTestClient, как показано в следующем примере:

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
class MyMockMvcTests {

    @Test
    void testWithMockMvc(@Autowired MockMvc mvc) throws Exception {
        mvc.perform(get("/")).andExpect(status().isOk()).andExpect(content().string("Hello World"));
    }

    // If Spring WebFlux is on the classpath, you can drive MVC tests with a WebTestClient
    @Test
    void testWithWebTestClient(@Autowired WebTestClient webClient) {
        webClient
                .get().uri("/")
                .exchange()
                .expectStatus().isOk()
                .expectBody(String.class).isEqualTo("Hello World");
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers

@SpringBootTest
@AutoConfigureMockMvc
class MyMockMvcTests {

    @Test
    fun testWithMockMvc(@Autowired mvc: MockMvc) {
        mvc.perform(MockMvcRequestBuilders.get("/")).andExpect(MockMvcResultMatchers.status().isOk)
            .andExpect(MockMvcResultMatchers.content().string("Hello World"))
    }

    // If Spring WebFlux is on the classpath, you can drive MVC tests with a WebTestClient

    @Test
    fun testWithWebTestClient(@Autowired webClient: WebTestClient) {
        webClient
            .get().uri("/")
            .exchange()
            .expectStatus().isOk
            .expectBody<String>().isEqualTo("Hello World")
    }

}
Если вы хотите сфокусироваться только на веб-слое и не запускать полный ApplicationContext, рассмотрите использование @WebMvcTest вместо этого.

С Spring WebFlux точками входа, вы можете использовать WebTestClient, как показано в следующем примере:

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;

@SpringBootTest
@AutoConfigureWebTestClient
class MyMockWebTestClientTests {

    @Test
    void exampleTest(@Autowired WebTestClient webClient) {
        webClient
            .get().uri("/")
            .exchange()
            .expectStatus().isOk()
            .expectBody(String.class).isEqualTo("Hello World");
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody

@SpringBootTest
@AutoConfigureWebTestClient
class MyMockWebTestClientTests {

    @Test
    fun exampleTest(@Autowired webClient: WebTestClient) {
        webClient
            .get().uri("/")
            .exchange()
            .expectStatus().isOk
            .expectBody<String>().isEqualTo("Hello World")
    }

}

Тестирование в эмулированной среде обычно быстрее, чем работа с полным контейнером сервлетов. Однако, поскольку эмуляция происходит на уровне Spring MVC, код, зависящий от поведения контейнера сервлетов на более низком уровне, не может быть напрямую протестирован с помощью MockMvc.

Например, обработка ошибок Spring Boot основана на поддержке «страницы ошибки», предоставляемой контейнером сервлетов. Это означает, что, хотя вы можете протестировать, что ваш слой MVC выбрасывает и обрабатывает исключения как ожидается, вы не можете напрямую проверить, что определённая собственная страница ошибки отображается. Если вам нужно протестировать эти проблемы более низкого уровня, вы можете запустить полностью работающий сервер, как описано в следующем разделе.

8.3.7. Тестирование с запущенным сервером

Если вам нужно запустить полностью работающий сервер, рекомендуется использовать случайные порты. Если вы используете @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT), доступный порт выбирается случайным образом каждый раз, когда запускается ваш тест.

Аннотация @LocalServerPort может быть использована для инъекции фактического используемого порта в ваш тест. Для удобства, тесты, которые нуждаются в выполнении REST-запросов к запущенному серверу, могут дополнительно @Autowire WebTestClient, который разрешает относительные ссылки к запущенному серверу и поставляется с отдельным API для проверки ответов, как показано в следующем примере:

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.web.reactive.server.WebTestClient;

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortWebTestClientTests {

    @Test
    void exampleTest(@Autowired WebTestClient webClient) {
        webClient
            .get().uri("/")
            .exchange()
            .expectStatus().isOk()
            .expectBody(String.class).isEqualTo("Hello World");
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortWebTestClientTests {

    @Test
    fun exampleTest(@Autowired webClient: WebTestClient) {
        webClient
            .get().uri("/")
            .exchange()
            .expectStatus().isOk
            .expectBody<String>().isEqualTo("Hello World")
    }

}
WebTestClient может быть использован как для живых серверов, так и для эмулированных сред.

Эта настройка требует spring-webflux в классе. Если вы не можете или не хотите добавлять webflux, Spring Boot также предоставляет TestRestTemplate возможность:

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.client.TestRestTemplate;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortTestRestTemplateTests {

    @Test
    void exampleTest(@Autowired TestRestTemplate restTemplate) {
        String body = restTemplate.getForObject("/", String.class);
        assertThat(body).isEqualTo("Hello World");
    }

}
Kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment
import org.springframework.boot.test.web.client.TestRestTemplate

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MyRandomPortTestRestTemplateTests {

    @Test
    fun exampleTest(@Autowired restTemplate: TestRestTemplate) {
        val body = restTemplate.getForObject("/", String::class.java)
        assertThat(body).isEqualTo("Hello World")
    }

}

8.3.8. Настройка WebTestClient

Для настройки компонента WebTestClient, настройте компонент WebTestClientBuilderCustomizer. Любые такие компоненты вызываются с помощью WebTestClient.Builder, используемого для создания WebTestClient.

8.3.9. Использование JMX

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

Java
import javax.management.MBeanServer;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(properties = "spring.jmx.enabled=true")
@DirtiesContext
class MyJmxTests {

    @Autowired
    private MBeanServer mBeanServer;

    @Test
    void exampleTest() {
        assertThat(this.mBeanServer.getDomains()).contains("java.lang");
        // ...
    }

}
Kotlin
import javax.management.MBeanServer

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.annotation.DirtiesContext

@SpringBootTest(properties = ["spring.jmx.enabled=true"])
@DirtiesContext
class MyJmxTests(@Autowired val mBeanServer: MBeanServer) {

    @Test
    fun exampleTest() {
        assertThat(mBeanServer.domains).contains("java.lang")
        // ...
    }

}

8.3.10. Использование метрик

Независимо от вашего classpath, реестры счетчиков, за исключением реестров в памяти, не настраиваются автоматически при использовании @SpringBootTest.

Если вам нужно экспортировать метрики в другой бэкенд в рамках интеграционного теста, отметьте его аннотацией @AutoConfigureObservability.

8.3.11. Использование отслеживания

Независимо от вашего classpath, отслеживание не настраивается автоматически при использовании @SpringBootTest.

Если вам нужно отслеживание в рамках интеграционного теста, отметьте его аннотацией @AutoConfigureObservability.

8.3.12. Моделирование и подглядывание за биндами

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

Spring Boot включает аннотацию @MockBean, которая может быть использована для определения модели Mockito для бинда внутри вашего ApplicationContext. Вы можете использовать аннотацию для добавления новых бинов или замены одного существующего определения бинда. Аннотация может быть использована напрямую на тестовых классах, на полях в вашем тесте или на @Configuration классах и полях. При использовании на поле экземпляр созданной модели также инжектируется. Модели бинов автоматически сбрасываются после каждого тестового метода.

Если ваш тест использует одну из тестовых аннотаций Spring Boot (таких как @SpringBootTest), эта функция автоматически включена. Для использования этой функции с другим расположением необходимо явно добавить слушатели, как показано в следующем примере:

Java
import org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener;
import org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;

@ContextConfiguration(classes = MyConfig.class)
@TestExecutionListeners({ MockitoTestExecutionListener.class, ResetMocksTestExecutionListener.class })
class MyTests {

    // ...

}
Kotlin
import org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener
import org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.TestExecutionListeners

@ContextConfiguration(classes = [MyConfig::class])
@TestExecutionListeners(
    MockitoTestExecutionListener::class,
    ResetMocksTestExecutionListener::class
)
class MyTests {

    // ...

}

Следующий пример заменяет существующий RemoteService бинд моделью:

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;

@SpringBootTest
class MyTests {

    @Autowired
    private Reverser reverser;

    @MockBean
    private RemoteService remoteService;

    @Test
    void exampleTest() {
        given(this.remoteService.getValue()).willReturn("spring");
        String reverse = this.reverser.getReverseValue(); // Calls injected RemoteService
        assertThat(reverse).isEqualTo("gnirps");
    }

}
Kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.mockito.BDDMockito.given
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.mock.mockito.MockBean

@SpringBootTest
class MyTests(@Autowired val reverser: Reverser, @MockBean val remoteService: RemoteService) {

    @Test
    fun exampleTest() {
        given(remoteService.value).willReturn("spring")
        val reverse = reverser.reverseValue // Calls injected RemoteService
        assertThat(reverse).isEqualTo("gnirps")
    }

}
@MockBean не может быть использована для моделирования поведения бинда, который выполняется во время обновления контекста приложения. К моменту выполнения теста обновление контекста приложения уже завершено, и слишком поздно настраивать смоделированное поведение. В этой ситуации рекомендуется использовать метод @Bean для создания и настройки модели.

Кроме того, вы можете использовать @SpyBean для обертывания любого существующего бинда с помощью модели Mockito spy. Смотрите Javadoc для получения полной информации.

Прокси CGLib, такие как те, что созданы для бинов со scoped, объявляют проксированные методы как final. Это предотвращает правильную работу Mockito, так как оно не может смоделировать или подглядывать за final методами в своей конфигурации по умолчанию. Если вы хотите смоделировать или подглядывать за таким бином, настройте Mockito, чтобы использовать его встроенный создатель модели, добавив org.mockito:mockito-inline в зависимости ваших тестов. Это позволит Mockito моделировать и подглядывать за final методами.
Хотя тестовая среда Spring кэширует контексты приложения между тестами и повторно использует контекст для тестов, которые используют одну и ту же конфигурацию, использование @MockBean или @SpyBean влияет на ключ кэша, что, скорее всего, увеличит количество контекстов.
Если вы используете @SpyBean для подглядывания за бином с @Cacheable методами, которые ссылаются на параметры по имени, ваше приложение должно быть скомпилировано с -parameters. Это гарантирует, что имена параметров доступны инфраструктуре кэширования после подглядывания за бином.
Когда вы используете @SpyBean для подглядывания за бином, проксированным Spring, вам может потребоваться удалить прокси Spring в некоторых ситуациях, например, при настройке ожиданий с помощью given или when. Используйте AopTestUtils.getTargetObject(yourProxiedSpy) для этого.

8.3.13. Автоконфигурируемые тесты

Система автоконфигурации Spring Boot хорошо работает для приложений, но иногда может быть слишком сложной для тестов. Зачастую полезно загрузить только те части конфигурации, которые необходимы для тестирования "фрагмента" вашего приложения. Например, вы можете захотеть проверить, что контроллеры Spring MVC правильно отображают URL-адреса, и не хотите вовлекать вызовы к базе данных в эти тесты, или вы можете захотеть протестировать сущности JPA, и вас не интересует веб-слой при запуске этих тестов.

Модуль spring-boot-test-autoconfigure включает ряд аннотаций, которые можно использовать для автоматической конфигурации таких "фрагментов". Каждая из них работает аналогичным образом, предоставляя аннотацию @…​Test, которая загружает ApplicationContext, и одну или несколько аннотаций @AutoConfigure…​, которые можно использовать для настройки параметров автоконфигурации.

Каждый фрагмент ограничивает сканирование компонентов соответствующими компонентами и загружает очень ограниченный набор классов автоконфигурации. Если вам нужно исключить один из них, большинство аннотаций @…​Test предоставляют атрибут excludeAutoConfiguration. В качестве альтернативы, вы можете использовать @ImportAutoConfiguration#exclude.
Включение нескольких "фрагментов" с использованием нескольких аннотаций @…​Test в одном тесте не поддерживается. Если вам нужны несколько "фрагментов", выберите одну из аннотаций @…​Test и вручную включите аннотации @AutoConfigure…​ других "фрагментов".
Также возможно использовать аннотации @AutoConfigure…​ со стандартной аннотацией @SpringBootTest. Вы можете использовать это сочетание, если вас не интересует "фрагментация" вашего приложения, но вы хотите некоторые из автоконфигурируемых тестовых бинов.

8.3.14. Автоконфигурируемые тесты JSON

Для проверки корректности сериализации и десериализации объектов JSON вы можете использовать аннотацию @JsonTest. @JsonTest автоматически настраивает доступный поддерживаемый маппер JSON, который может быть одной из следующих библиотек:

  • Jackson ObjectMapper, любые @JsonComponent бинды и любые Jackson Module

  • Gson

  • Jsonb

Список автоконфигураций, которые включены аннотацией @JsonTest, можно найти в приложении.

Если вам нужно настроить элементы автоконфигурации, вы можете использовать аннотацию @AutoConfigureJsonTesters.

Spring Boot включает помощники на основе AssertJ, которые работают с библиотеками JSONAssert и JsonPath для проверки того, что JSON отображается как ожидается. Классы JacksonTester, GsonTester, JsonbTester и BasicJsonTester могут быть использованы для Jackson, Gson, Jsonb и строк соответственно. Любые вспомогательные поля в тестовом классе могут быть @Autowired при использовании @JsonTest. Следующий пример демонстрирует тестовый класс для Jackson:

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.json.JsonTest;
import org.springframework.boot.test.json.JacksonTester;

import static org.assertj.core.api.Assertions.assertThat;

@JsonTest
class MyJsonTests {

    @Autowired
    private JacksonTester<VehicleDetails> json;

    @Test
    void serialize() throws Exception {
        VehicleDetails details = new VehicleDetails("Honda", "Civic");
        // Assert against a `.json` file in the same package as the test
        assertThat(this.json.write(details)).isEqualToJson("expected.json");
        // Or use JSON path based assertions
        assertThat(this.json.write(details)).hasJsonPathStringValue("@.make");
        assertThat(this.json.write(details)).extractingJsonPathStringValue("@.make").isEqualTo("Honda");
    }

    @Test
    void deserialize() throws Exception {
        String content = "{\"make\":\"Ford\",\"model\":\"Focus\"}";
        assertThat(this.json.parse(content)).isEqualTo(new VehicleDetails("Ford", "Focus"));
        assertThat(this.json.parseObject(content).getMake()).isEqualTo("Ford");
    }

}
Kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.json.JsonTest
import org.springframework.boot.test.json.JacksonTester

@JsonTest
class MyJsonTests(@Autowired val json: JacksonTester<VehicleDetails>) {

    @Test
    fun serialize() {
        val details = VehicleDetails("Honda", "Civic")
        // Assert against a `.json` file in the same package as the test
        assertThat(json.write(details)).isEqualToJson("expected.json")
        // Or use JSON path based assertions
        assertThat(json.write(details)).hasJsonPathStringValue("@.make")
        assertThat(json.write(details)).extractingJsonPathStringValue("@.make").isEqualTo("Honda")
    }

    @Test
    fun deserialize() {
        val content = "{\"make\":\"Ford\",\"model\":\"Focus\"}"
        assertThat(json.parse(content)).isEqualTo(VehicleDetails("Ford", "Focus"))
        assertThat(json.parseObject(content).make).isEqualTo("Ford")
    }

}
Классы помощников JSON также могут быть использованы непосредственно в стандартных юнит-тестах. Для этого вызовите метод initFields помощника в вашем методе @Before, если вы не используете @JsonTest.

Если вы используете помощники Spring Boot на основе AssertJ для проверки числового значения по заданному пути JSON, вы, возможно, не сможете использовать isEqualTo в зависимости от типа. Вместо этого вы можете использовать satisfies AssertJ для проверки того, что значение соответствует заданному условию. Например, следующий пример проверяет, что фактическое число является значением типа float, близким к 0.15 с отклонением в 0.01.

Java
@Test
void someTest() throws Exception {
    SomeObject value = new SomeObject(0.152f);
    assertThat(this.json.write(value)).extractingJsonPathNumberValue("@.test.numberValue")
        .satisfies((number) -> assertThat(number.floatValue()).isCloseTo(0.15f, within(0.01f)));
}
Kotlin
@Test
fun someTest() {
    val value = SomeObject(0.152f)
    assertThat(json.write(value)).extractingJsonPathNumberValue("@.test.numberValue")
        .satisfies(ThrowingConsumer { number ->
            assertThat(number.toFloat()).isCloseTo(0.15f, within(0.01f))
        })
}

8.3.15. Автоконфигурируемые тесты Spring MVC

Для проверки корректной работы контроллеров Spring MVC используйте аннотацию @WebMvcTest. @WebMvcTest автоматически настраивает инфраструктуру Spring MVC и ограничивает сканирование бинов до @Controller, @ControllerAdvice, @JsonComponent, Converter, GenericConverter, Filter, HandlerInterceptor, WebMvcConfigurer, WebMvcRegistrations и HandlerMethodArgumentResolver. Регулярные бины @Component и @ConfigurationProperties не будут сканироваться при использовании аннотации @WebMvcTest. Аннотация @EnableConfigurationProperties может быть использована для включения бинов @ConfigurationProperties.

Список настроек автоконфигурации, активируемых @WebMvcTest, можно найти в приложении.
Если вам необходимо зарегистрировать дополнительные компоненты, такие как Jackson Module, вы можете импортировать дополнительные конфигурационные классы, используя @Import в вашем тесте.

Часто @WebMvcTest ограничивается одним контроллером и используется в сочетании с @MockBean для предоставления имитирующих реализаций необходимых коллабораторов.

@WebMvcTest также автоматически настраивает MockMvc. Мок MVC предоставляет мощный способ быстрого тестирования контроллеров MVC без необходимости запуска полного HTTP-сервера.

Вы также можете автоматически настроить MockMvc в не-@WebMvcTest (таком как @SpringBootTest) путем аннотирования его @AutoConfigureMockMvc. Следующий пример использует MockMvc:
Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(UserVehicleController.class)
class MyControllerTests {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private UserVehicleService userVehicleService;

    @Test
    void testExample() throws Exception {
        given(this.userVehicleService.getVehicleDetails("sboot"))
            .willReturn(new VehicleDetails("Honda", "Civic"));
        this.mvc.perform(get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN))
            .andExpect(status().isOk())
            .andExpect(content().string("Honda Civic"));
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.mockito.BDDMockito.given
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers

@WebMvcTest(UserVehicleController::class)
class MyControllerTests(@Autowired val mvc: MockMvc) {

    @MockBean
    lateinit var userVehicleService: UserVehicleService

    @Test
    fun testExample() {
        given(userVehicleService.getVehicleDetails("sboot"))
            .willReturn(VehicleDetails("Honda", "Civic"))
        mvc.perform(MockMvcRequestBuilders.get("/sboot/vehicle").accept(MediaType.TEXT_PLAIN))
            .andExpect(MockMvcResultMatchers.status().isOk)
            .andExpect(MockMvcResultMatchers.content().string("Honda Civic"))
    }

}
Если вам нужно настроить элементы автоконфигурации (например, когда должны применяться фильтры сервлетов), вы можете использовать атрибуты в аннотации @AutoConfigureMockMvc.

Если вы используете HtmlUnit и Selenium, автоконфигурация также предоставляет бин HtmlUnit WebClient и/или бин Selenium WebDriver. Следующий пример использует HtmlUnit:

Java
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;

@WebMvcTest(UserVehicleController.class)
class MyHtmlUnitTests {

    @Autowired
    private WebClient webClient;

    @MockBean
    private UserVehicleService userVehicleService;

    @Test
    void testExample() throws Exception {
        given(this.userVehicleService.getVehicleDetails("sboot")).willReturn(new VehicleDetails("Honda", "Civic"));
        HtmlPage page = this.webClient.getPage("/sboot/vehicle.html");
        assertThat(page.getBody().getTextContent()).isEqualTo("Honda Civic");
    }

}
Kotlin
import com.gargoylesoftware.htmlunit.WebClient
import com.gargoylesoftware.htmlunit.html.HtmlPage
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.mockito.BDDMockito.given
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.boot.test.mock.mockito.MockBean

@WebMvcTest(UserVehicleController::class)
class MyHtmlUnitTests(@Autowired val webClient: WebClient) {

    @MockBean
    lateinit var userVehicleService: UserVehicleService

    @Test
    fun testExample() {
        given(userVehicleService.getVehicleDetails("sboot")).willReturn(VehicleDetails("Honda", "Civic"))
        val page = webClient.getPage<HtmlPage>("/sboot/vehicle.html")
        assertThat(page.body.textContent).isEqualTo("Honda Civic")
    }

}
По умолчанию Spring Boot помещает бины WebDriver в специальный «scope», чтобы обеспечить выход драйвера после каждого теста и инъекцию нового экземпляра. Если вам не нужно такое поведение, добавьте @Scope("singleton") в ваше определение WebDriver @Bean.
Scope webDriver, созданный Spring Boot, заменит любой определенный вами scope с таким же именем. Если вы определите свой собственный scope webDriver, он может перестать работать при использовании @WebMvcTest.

Если у вас Spring Security в classpath, @WebMvcTest также будет сканировать бины WebSecurityConfigurer. Вместо того, чтобы полностью отключать безопасность для таких тестов, вы можете использовать поддержку Spring Security. Подробнее о том, как использовать поддержку Spring Security MockMvc, можно найти в разделе howto.html.

Иногда написания тестов Spring MVC недостаточно; Spring Boot может помочь вам выполнить полные тесты «конец-конец» с реальным сервером.

8.3.16. Автоконфигурируемые тесты Spring WebFlux

Для проверки корректной работы контроллеров Spring WebFlux используйте аннотацию @WebFluxTest. @WebFluxTest автоматически настраивает инфраструктуру Spring WebFlux и ограничивает сканирование бинов до @Controller, @ControllerAdvice, @JsonComponent, Converter, GenericConverter, WebFilter и WebFluxConfigurer. Регулярные бины @Component и @ConfigurationProperties не будут сканироваться при использовании аннотации @WebFluxTest. @EnableConfigurationProperties может быть использован для включения бинов @ConfigurationProperties.

Список автоконфигураций, активируемых @WebFluxTest, можно найти в приложении.
Если вам нужно зарегистрировать дополнительные компоненты, такие как Jackson Module, вы можете импортировать дополнительные конфигурационные классы, используя @Import в вашем тесте.

Часто @WebFluxTest ограничивается одним контроллером и используется в сочетании с аннотацией @MockBean для предоставления имитационных реализаций необходимых коллабораторов.

@WebFluxTest также автоматически настраивает WebTestClient, что предоставляет мощный способ быстрого тестирования контроллеров WebFlux без необходимости запуска полного HTTP-сервера.

Вы также можете настроить WebTestClient в не-@WebFluxTest (таком как @SpringBootTest) путем аннотирования его @AutoConfigureWebTestClient. Следующий пример показывает класс, использующий как @WebFluxTest, так и WebTestClient:
Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;

import static org.mockito.BDDMockito.given;

@WebFluxTest(UserVehicleController.class)
class MyControllerTests {

    @Autowired
    private WebTestClient webClient;

    @MockBean
    private UserVehicleService userVehicleService;

    @Test
    void testExample() {
        given(this.userVehicleService.getVehicleDetails("sboot"))
            .willReturn(new VehicleDetails("Honda", "Civic"));
        this.webClient.get().uri("/sboot/vehicle").accept(MediaType.TEXT_PLAIN).exchange()
            .expectStatus().isOk()
            .expectBody(String.class).isEqualTo("Honda Civic");
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.mockito.BDDMockito.given
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.http.MediaType
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.test.web.reactive.server.expectBody

@WebFluxTest(UserVehicleController::class)
class MyControllerTests(@Autowired val webClient: WebTestClient) {

    @MockBean
    lateinit var userVehicleService: UserVehicleService

    @Test
    fun testExample() {
        given(userVehicleService.getVehicleDetails("sboot"))
            .willReturn(VehicleDetails("Honda", "Civic"))
        webClient.get().uri("/sboot/vehicle").accept(MediaType.TEXT_PLAIN).exchange()
            .expectStatus().isOk
            .expectBody<String>().isEqualTo("Honda Civic")
    }

}
Данная настройка поддерживается только приложениями WebFlux, так как использование WebTestClient в имитируемом веб-приложении в настоящее время работает только с WebFlux.
@WebFluxTest не может обнаружить маршруты, зарегистрированные через функциональную веб-фреймворк. Для тестирования бинов RouterFunction в контексте рассмотрите импорт вашего RouterFunction самостоятельно, используя @Import или @SpringBootTest.
@WebFluxTest не может обнаружить пользовательскую конфигурацию безопасности, зарегистрированную как @Bean типа SecurityWebFilterChain. Чтобы включить её в тест, необходимо импортировать конфигурацию, регистрирующую бин, используя @Import или @SpringBootTest.
Иногда написания тестов Spring WebFlux недостаточно; Spring Boot может помочь выполнить полные тесты «конец-конец» с реальным сервером.

8.3.17. Автоконфигурируемые тесты Spring GraphQL

Spring GraphQL предлагает специализированный модуль поддержки тестирования; вам нужно добавить его в свой проект:

Maven
<dependencies>
  <dependency>
    <groupId>org.springframework.graphql</groupId>
    <artifactId>spring-graphql-test</artifactId>
    <scope>test</scope>
  </dependency>
  <!-- Unless already present in the compile scope -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
    <scope>test</scope>
  </dependency>
</dependencies>
Gradle
dependencies {
  testImplementation("org.springframework.graphql:spring-graphql-test")
  // Unless already present in the implementation configuration
  testImplementation("org.springframework.boot:spring-boot-starter-webflux")
}

Этот модуль тестирования поставляет GraphQlTester. Тестер широко используется в тестах, поэтому обязательно ознакомьтесь с его использованием. Существует GraphQlTester варианты, и Spring Boot будет автоматически настраивать их в зависимости от типа тестов:

  • ExecutionGraphQlServiceTester выполняет тесты на стороне сервера без клиента и транспорта

  • HttpGraphQlTester выполняет тесты с клиентом, который подключается к серверу, с живым сервером или без него

Spring Boot помогает вам тестировать ваши Spring GraphQL контроллеры с помощью аннотации @GraphQlTest. @GraphQlTest автоматически настраивает инфраструктуру Spring GraphQL без участия транспорта или сервера. Это ограничивает сканируемые бин к @Controller, RuntimeWiringConfigurer, JsonComponent, Converter, GenericConverter, DataFetcherExceptionResolver, Instrumentation и GraphQlSourceBuilderCustomizer. Регулярные бин @Component и @ConfigurationProperties не сканируются при использовании аннотации @GraphQlTest. @EnableConfigurationProperties можно использовать для включения бинов @ConfigurationProperties.

Список автоконфигураций, которые активируются @GraphQlTest, можно найти в приложении.

Часто @GraphQlTest ограничивается набором контроллеров и используется в сочетании с аннотацией @MockBean для предоставления моковых реализаций необходимых коллабораторов.

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.docs.web.graphql.runtimewiring.GreetingController;
import org.springframework.boot.test.autoconfigure.graphql.GraphQlTest;
import org.springframework.graphql.test.tester.GraphQlTester;

@GraphQlTest(GreetingController.class)
class GreetingControllerTests {

    @Autowired
    private GraphQlTester graphQlTester;

    @Test
    void shouldGreetWithSpecificName() {
        this.graphQlTester.document("{ greeting(name: \"Alice\") } ")
            .execute()
            .path("greeting")
            .entity(String.class)
            .isEqualTo("Hello, Alice!");
    }

    @Test
    void shouldGreetWithDefaultName() {
        this.graphQlTester.document("{ greeting } ")
            .execute()
            .path("greeting")
            .entity(String.class)
            .isEqualTo("Hello, Spring!");
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.docs.web.graphql.runtimewiring.GreetingController
import org.springframework.boot.test.autoconfigure.graphql.GraphQlTest
import org.springframework.graphql.test.tester.GraphQlTester

@GraphQlTest(GreetingController::class)
internal class GreetingControllerTests {

    @Autowired
    lateinit var graphQlTester: GraphQlTester

    @Test
    fun shouldGreetWithSpecificName() {
        graphQlTester.document("{ greeting(name: \"Alice\") } ").execute().path("greeting").entity(String::class.java)
                .isEqualTo("Hello, Alice!")
    }

    @Test
    fun shouldGreetWithDefaultName() {
        graphQlTester.document("{ greeting } ").execute().path("greeting").entity(String::class.java)
                .isEqualTo("Hello, Spring!")
    }

}

@SpringBootTest тесты являются полными интеграционными тестами и включают весь проект. При использовании случайного или заданного порта настраивается живой сервер, и бин HttpGraphQlTester автоматически добавляется, так что вы можете использовать его для тестирования вашего сервера. При конфигурации среды MOCK вы также можете запросить бин HttpGraphQlTester, добавив аннотацию @AutoConfigureHttpGraphQlTester к вашему тестовому классу:

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureHttpGraphQlTester;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.graphql.test.tester.HttpGraphQlTester;

@AutoConfigureHttpGraphQlTester
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
class GraphQlIntegrationTests {

    @Test
    void shouldGreetWithSpecificName(@Autowired HttpGraphQlTester graphQlTester) {
        HttpGraphQlTester authenticatedTester = graphQlTester.mutate()
            .webTestClient((client) -> client.defaultHeaders((headers) -> headers.setBasicAuth("admin", "ilovespring")))
            .build();
        authenticatedTester.document("{ greeting(name: \"Alice\") } ")
            .execute()
            .path("greeting")
            .entity(String.class)
            .isEqualTo("Hello, Alice!");
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.graphql.tester.AutoConfigureHttpGraphQlTester
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.graphql.test.tester.HttpGraphQlTester
import org.springframework.http.HttpHeaders
import org.springframework.test.web.reactive.server.WebTestClient

@AutoConfigureHttpGraphQlTester
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
class GraphQlIntegrationTests {

    @Test
    fun shouldGreetWithSpecificName(@Autowired graphQlTester: HttpGraphQlTester) {
        val authenticatedTester = graphQlTester.mutate()
            .webTestClient { client: WebTestClient.Builder ->
                client.defaultHeaders { headers: HttpHeaders ->
                    headers.setBasicAuth("admin", "ilovespring")
                }
            }.build()
        authenticatedTester.document("{ greeting(name: \"Alice\") } ").execute()
            .path("greeting").entity(String::class.java).isEqualTo("Hello, Alice!")
    }
}

8.3.18. Автоконфигурируемые тесты Data Cassandra

Вы можете использовать @DataCassandraTest для тестирования приложений Cassandra. По умолчанию он настраивает CassandraTemplate, сканирует классы @Table и настраивает репозитории Spring Data Cassandra. Регулярные бин @Component и @ConfigurationProperties не сканируются при использовании аннотации @DataCassandraTest. @EnableConfigurationProperties можно использовать для включения бинов @ConfigurationProperties. (Подробнее о работе с Cassandra в Spring Boot см. "data.html".)

Список настроек автоконфигурации, которые активирует @DataCassandraTest, можно найти в приложении.

Следующий пример демонстрирует типичную настройку для использования тестов Cassandra в Spring Boot:

Java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.cassandra.DataCassandraTest;

@DataCassandraTest
class MyDataCassandraTests {

    @Autowired
    private SomeRepository repository;

}
Kotlin
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.cassandra.DataCassandraTest

@DataCassandraTest
class MyDataCassandraTests(@Autowired val repository: SomeRepository)

8.3.19. Автоконфигурируемые тесты Data Couchbase

Вы можете использовать @DataCouchbaseTest для тестирования приложений Couchbase. По умолчанию он настраивает CouchbaseTemplate или ReactiveCouchbaseTemplate, сканирует классы @Document и настраивает репозитории Spring Data Couchbase. Регулярные бин @Component и @ConfigurationProperties не сканируются при использовании аннотации @DataCouchbaseTest. @EnableConfigurationProperties можно использовать для включения бинов @ConfigurationProperties. (Подробнее о работе с Couchbase в Spring Boot см. "data.html" в этой главе.)

Список настроек автоконфигурации, которые активирует @DataCouchbaseTest, можно найти в приложении.

Следующий пример демонстрирует типичную настройку для использования тестов Couchbase в Spring Boot:

Java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.couchbase.DataCouchbaseTest;

@DataCouchbaseTest
class MyDataCouchbaseTests {

    @Autowired
    private SomeRepository repository;

    // ...

}
Kotlin
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.couchbase.DataCouchbaseTest

@DataCouchbaseTest
class MyDataCouchbaseTests(@Autowired val repository: SomeRepository) {

    // ...

}

8.3.20. Автоконфигурируемые тесты Data Elasticsearch

Вы можете использовать @DataElasticsearchTest для тестирования приложений Elasticsearch. По умолчанию он настраивает ElasticsearchRestTemplate, сканирует классы @Document и настраивает репозитории Spring Data Elasticsearch. Регулярные бин @Component и @ConfigurationProperties не сканируются при использовании аннотации @DataElasticsearchTest. @EnableConfigurationProperties можно использовать для включения бинов @ConfigurationProperties. (Подробнее о работе с Elasticsearch в Spring Boot см. "data.html" в этой главе.)

Список настроек автоконфигурации, которые активирует @DataElasticsearchTest, можно найти в приложении.

Следующий пример демонстрирует типичную настройку для использования тестов Elasticsearch в Spring Boot:

Java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.elasticsearch.DataElasticsearchTest;

@DataElasticsearchTest
class MyDataElasticsearchTests {

    @Autowired
    private SomeRepository repository;

    // ...

}
Kotlin
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.elasticsearch.DataElasticsearchTest

@DataElasticsearchTest
class MyDataElasticsearchTests(@Autowired val repository: SomeRepository) {

    // ...

}

8.3.21. Автонастроенные тесты Data JPA

Вы можете использовать аннотацию @DataJpaTest для тестирования приложений JPA. По умолчанию она сканирует классы @Entity и настраивает репозитории Spring Data JPA. Если на пути к классу доступна встроенная база данных, она также настраивается. Запросы SQL по умолчанию регистрируются, если свойство spring.jpa.show-sql установлено в значение true. Это можно отключить, используя атрибут showSql аннотации.

Обычные @Component и @ConfigurationProperties бины не сканируются при использовании аннотации @DataJpaTest. @EnableConfigurationProperties можно использовать для включения @ConfigurationProperties бинов.

Список настроек автоконфигурации, включенных аннотацией @DataJpaTest, можно найти в приложении.

По умолчанию тесты Data JPA транзакционные и откатываются в конце каждого теста. Подробности см. в соответствующем разделе документации Spring Framework. Если этого не требуется, вы можете отключить управление транзакциями для теста или для всего класса следующим образом:

Java
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@DataJpaTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyNonTransactionalTests {

    // ...

}
Kotlin
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional

@DataJpaTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyNonTransactionalTests {

    // ...

}

Тесты Data JPA также могут инжектировать бин TestEntityManager, что предоставляет альтернативу стандартному JPA EntityManager, специально разработанному для тестов.

TestEntityManager также может быть автоматически сконфигурирован для любого вашего тестового класса на основе Spring путем добавления @AutoConfigureTestEntityManager. При этом убедитесь, что ваш тест выполняется в транзакции, например, добавив @Transactional к вашему тестовому классу или методу.

Также доступен JdbcTemplate, если вам это нужно. Следующий пример демонстрирует использование аннотации @DataJpaTest:

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;

import static org.assertj.core.api.Assertions.assertThat;

@DataJpaTest
class MyRepositoryTests {

    @Autowired
    private TestEntityManager entityManager;

    @Autowired
    private UserRepository repository;

    @Test
    void testExample() {
        this.entityManager.persist(new User("sboot", "1234"));
        User user = this.repository.findByUsername("sboot");
        assertThat(user.getUsername()).isEqualTo("sboot");
        assertThat(user.getEmployeeNumber()).isEqualTo("1234");
    }

}
Kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager

@DataJpaTest
class MyRepositoryTests(@Autowired val entityManager: TestEntityManager, @Autowired val repository: UserRepository) {

    @Test
    fun testExample() {
        entityManager.persist(User("sboot", "1234"))
        val user = repository.findByUsername("sboot")
        assertThat(user?.username).isEqualTo("sboot")
        assertThat(user?.employeeNumber).isEqualTo("1234")
    }

}

Встроенные базы данных в памяти обычно хорошо подходят для тестов, так как они быстры и не требуют установки. Однако, если вы предпочитаете запускать тесты против реальной базы данных, вы можете использовать аннотацию @AutoConfigureTestDatabase, как показано в следующем примере:

Java
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;

@DataJpaTest
@AutoConfigureTestDatabase(replace = Replace.NONE)
class MyRepositoryTests {

    // ...

}
Kotlin
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest

@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class MyRepositoryTests {

    // ...

}

8.3.22. Автонастроенные тесты JDBC

@JdbcTest аналогичен @DataJpaTest, но предназначен для тестов, которые требуют только DataSource и не используют Spring Data JDBC. По умолчанию он настраивает встроенную базу данных в памяти и JdbcTemplate. Обычные @Component и @ConfigurationProperties бины не сканируются при использовании аннотации @JdbcTest. @EnableConfigurationProperties можно использовать для включения @ConfigurationProperties бинов.

Список автоконфигураций, включённых аннотацией @JdbcTest, можно найти в приложении.

По умолчанию JDBC-тесты транзакционные и откатываются в конце каждого теста. Подробности см. в соответствующем разделе документации Spring Framework. Если это не требуется, можно отключить управление транзакциями для теста или для всего класса следующим образом:

Java
import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@JdbcTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyTransactionalTests {

}
Kotlin
import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional

@JdbcTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyTransactionalTests

Если вы предпочитаете запускать тест против реальной базы данных, вы можете использовать аннотацию @AutoConfigureTestDatabase так же, как и для DataJpaTest. (См. "Автонастроенные тесты Data JPA".)

8.3.23. Автонастроенные тесты Data JDBC

@DataJdbcTest аналогичен @JdbcTest, но предназначен для тестов, использующих репозитории Spring Data JDBC. По умолчанию он настраивает встроенную базу данных в памяти, JdbcTemplate и репозитории Spring Data JDBC. Только подклассы AbstractJdbcConfiguration сканируются при использовании аннотации @DataJdbcTest; обычные @Component и @ConfigurationProperties бины не сканируются. @EnableConfigurationProperties можно использовать для включения @ConfigurationProperties бинов.

Список автоконфигураций, включённых аннотацией @DataJdbcTest, можно найти в приложении.

По умолчанию тесты Data JDBC транзакционные и откатываются в конце каждого теста. Подробности см. в соответствующем разделе документации Spring Framework. Если этого не требуется, можно отключить управление транзакциями для теста или для всего тестового класса, как показано в примере JDBC.

Если вы предпочитаете запускать тест против реальной базы данных, вы можете использовать аннотацию @AutoConfigureTestDatabase аналогично DataJpaTest. (См. "Автонастроенные тесты Data JPA".)

8.3.24. Автонастроенные тесты Data R2DBC

@DataR2dbcTest аналогичен @DataJdbcTest, но предназначен для тестов, использующих репозитории Spring Data R2DBC. По умолчанию он настраивает встроенную базу данных в памяти, R2dbcEntityTemplate и репозитории Spring Data R2DBC. Обычные @Component и @ConfigurationProperties бины не сканируются при использовании аннотации @DataR2dbcTest. @EnableConfigurationProperties можно использовать для включения @ConfigurationProperties бинов.

Список автоконфигураций, включённых аннотацией @DataR2dbcTest, можно найти в приложении.

По умолчанию тесты Data R2DBC не транзакционные.

Если вы предпочитаете запускать тест против реальной базы данных, вы можете использовать аннотацию @AutoConfigureTestDatabase аналогично DataJpaTest. (См. "Автонастроенные тесты Data JPA".)

8.3.25. Автонастроенные тесты jOOQ

Вы можете использовать @JooqTest аналогично @JdbcTest, но для тестов, связанных с jOOQ. Поскольку jOOQ сильно зависит от схемы на Java, соответствующей схеме базы данных, используется существующая DataSource. Если вы хотите заменить её встроенной базой данных, вы можете использовать @AutoConfigureTestDatabase для переопределения этих настроек. (Подробнее о работе с jOOQ в Spring Boot см. "data.html".) Обычные @Component и @ConfigurationProperties бины не сканируются при использовании аннотации @JooqTest. @EnableConfigurationProperties можно использовать для включения @ConfigurationProperties бинов.

Список автоконфигураций, включённых аннотацией @JooqTest, можно найти в приложении.

@JooqTest настраивает DSLContext. Следующий пример демонстрирует использование аннотации @JooqTest:

Java
import org.jooq.DSLContext;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jooq.JooqTest;

@JooqTest
class MyJooqTests {

    @Autowired
    private DSLContext dslContext;

    // ...

}
Kotlin
import org.jooq.DSLContext
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.jooq.JooqTest

@JooqTest
class MyJooqTests(@Autowired val dslContext: DSLContext) {

    // ...

}

Тесты jOOQ по умолчанию транзакционные и откатываются в конце каждого теста. Если это не требуется, можно отключить управление транзакциями для теста или для всего тестового класса, как показано в примере JDBC.

8.3.26. Автоконфигурированные тесты данных MongoDB

Вы можете использовать @DataMongoTest для тестирования приложений MongoDB. По умолчанию он настраивает MongoTemplate, сканирует классы @Document и настраивает репозитории Spring Data MongoDB. Регулярные @Component и @ConfigurationProperties бины не сканируются, когда используется аннотация @DataMongoTest. @EnableConfigurationProperties можно использовать для включения @ConfigurationProperties бинов. (Дополнительную информацию об использовании MongoDB с Spring Boot см. в "data.html".)

Список параметров автоконфигурации, включенных @DataMongoTest, можно найти в приложении.

Следующий класс демонстрирует использование аннотации @DataMongoTest:

Java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest;
import org.springframework.data.mongodb.core.MongoTemplate;

@DataMongoTest
class MyDataMongoDbTests {

    @Autowired
    private MongoTemplate mongoTemplate;

    // ...

}
Kotlin
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest
import org.springframework.data.mongodb.core.MongoTemplate

@DataMongoTest
class MyDataMongoDbTests(@Autowired val mongoTemplate: MongoTemplate) {

    // ...

}

8.3.27. Автоконфигурированные тесты данных Neo4j

Вы можете использовать @DataNeo4jTest для тестирования приложений Neo4j. По умолчанию он сканирует классы @Node и настраивает репозитории Spring Data Neo4j. Регулярные @Component и @ConfigurationProperties бины не сканируются, когда используется аннотация @DataNeo4jTest. @EnableConfigurationProperties можно использовать для включения @ConfigurationProperties бинов. (Дополнительную информацию об использовании Neo4J с Spring Boot см. в "data.html".)

Список параметров автоконфигурации, включенных @DataNeo4jTest, можно найти в приложении.

Следующий пример показывает типичную настройку для использования тестов Neo4J в Spring Boot:

Java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest;

@DataNeo4jTest
class MyDataNeo4jTests {

    @Autowired
    private SomeRepository repository;

    // ...

}
Kotlin
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest

@DataNeo4jTest
class MyDataNeo4jTests(@Autowired val repository: SomeRepository) {

    // ...

}

По умолчанию тесты данных Neo4j транзакционные и откатываются в конце каждого теста. Более подробную информацию см. в соответствующем разделе документации по Spring Framework. Если это не то, что вам нужно, вы можете отключить управление транзакциями для теста или для всего класса следующим образом:

Java
import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@DataNeo4jTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyDataNeo4jTests {

}
Kotlin
import org.springframework.boot.test.autoconfigure.data.neo4j.DataNeo4jTest
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional

@DataNeo4jTest
@Transactional(propagation = Propagation.NOT_SUPPORTED)
class MyDataNeo4jTests
Транзакционные тесты не поддерживаются с реактивным доступом. Если вы используете этот стиль, вы должны настроить @DataNeo4jTest тесты, как описано выше.

8.3.28. Автоконфигурированные тесты данных Redis

Вы можете использовать @DataRedisTest для тестирования приложений Redis. По умолчанию он сканирует классы @RedisHash и настраивает репозитории Spring Data Redis. Регулярные @Component и @ConfigurationProperties бины не сканируются, когда используется аннотация @DataRedisTest. @EnableConfigurationProperties можно использовать для включения @ConfigurationProperties бинов. (Дополнительную информацию об использовании Redis с Spring Boot см. в "data.html".)

Список параметров автоконфигурации, включенных @DataRedisTest, можно найти в приложении.

Следующий пример показывает использование аннотации @DataRedisTest:

Java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest;

@DataRedisTest
class MyDataRedisTests {

    @Autowired
    private SomeRepository repository;

    // ...

}
Kotlin
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.redis.DataRedisTest

@DataRedisTest
class MyDataRedisTests(@Autowired val repository: SomeRepository) {

    // ...

}

8.3.29. Автоконфигурированные тесты данных LDAP

Вы можете использовать @DataLdapTest для тестирования приложений LDAP. По умолчанию он настраивает встроенный LDAP в памяти (если доступен), настраивает LdapTemplate, сканирует классы @Entry и настраивает репозитории Spring Data LDAP. Регулярные @Component и @ConfigurationProperties бины не сканируются, когда используется аннотация @DataLdapTest. @EnableConfigurationProperties можно использовать для включения @ConfigurationProperties бинов. (Дополнительную информацию об использовании LDAP с Spring Boot см. в "data.html".)

Список параметров автоконфигурации, включенных @DataLdapTest, можно найти в приложении.

Следующий пример показывает использование аннотации @DataLdapTest:

Java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest;
import org.springframework.ldap.core.LdapTemplate;

@DataLdapTest
class MyDataLdapTests {

    @Autowired
    private LdapTemplate ldapTemplate;

    // ...

}
Kotlin
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest
import org.springframework.ldap.core.LdapTemplate

@DataLdapTest
class MyDataLdapTests(@Autowired val ldapTemplate: LdapTemplate) {

    // ...

}

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

Java
import org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration;
import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest;

@DataLdapTest(excludeAutoConfiguration = EmbeddedLdapAutoConfiguration.class)
class MyDataLdapTests {

    // ...

}
Kotlin
import org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration
import org.springframework.boot.test.autoconfigure.data.ldap.DataLdapTest

@DataLdapTest(excludeAutoConfiguration = [EmbeddedLdapAutoConfiguration::class])
class MyDataLdapTests {

    // ...

}

8.3.30. Автоконфигурированные REST-клиенты

Вы можете использовать аннотацию @RestClientTest для тестирования REST-клиентов. По умолчанию он автоматически настраивает поддержку Jackson, GSON и Jsonb, настраивает RestTemplateBuilder и добавляет поддержку MockRestServiceServer. Регулярные @Component и @ConfigurationProperties бины не сканируются при использовании аннотации @RestClientTest. @EnableConfigurationProperties можно использовать для включения @ConfigurationProperties бинов.

Список параметров автоконфигурации, включенных @RestClientTest, можно найти в приложении.

Конкретные бины, которые вы хотите протестировать, должны быть указаны с помощью атрибута value или components аннотации @RestClientTest, как показано в следующем примере:

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;

@RestClientTest(RemoteVehicleDetailsService.class)
class MyRestClientTests {

    @Autowired
    private RemoteVehicleDetailsService service;

    @Autowired
    private MockRestServiceServer server;

    @Test
    void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails() {
        this.server.expect(requestTo("/greet/details")).andRespond(withSuccess("hello", MediaType.TEXT_PLAIN));
        String greeting = this.service.callRestService();
        assertThat(greeting).isEqualTo("hello");
    }

}
Kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.client.RestClientTest
import org.springframework.http.MediaType
import org.springframework.test.web.client.MockRestServiceServer
import org.springframework.test.web.client.match.MockRestRequestMatchers
import org.springframework.test.web.client.response.MockRestResponseCreators

@RestClientTest(RemoteVehicleDetailsService::class)
class MyRestClientTests(
    @Autowired val service: RemoteVehicleDetailsService,
    @Autowired val server: MockRestServiceServer) {

    @Test
    fun getVehicleDetailsWhenResultIsSuccessShouldReturnDetails(): Unit {
        server.expect(MockRestRequestMatchers.requestTo("/greet/details"))
            .andRespond(MockRestResponseCreators.withSuccess("hello", MediaType.TEXT_PLAIN))
        val greeting = service.callRestService()
        assertThat(greeting).isEqualTo("hello")
    }

}

8.3.31. Автонастроенные тесты Spring REST Docs

Вы можете использовать аннотацию @AutoConfigureRestDocs, чтобы использовать Spring REST Docs в своих тестах с Mock MVC, REST Assured или WebTestClient. Она избавляет от необходимости использования JUnit-расширения в Spring REST Docs.

@AutoConfigureRestDocs можно использовать для переопределения стандартной директории вывода (target/generated-snippets если вы используете Maven или build/generated-snippets если используете Gradle). Также можно настроить хост, схему и порт, которые будут отображаться в любых документированных URI.

Автонастроенные тесты Spring REST Docs с Mock MVC

@AutoConfigureRestDocs настраивает бин MockMvc для использования Spring REST Docs при тестировании веб-приложений на основе сервлетов. Вы можете инжектировать его, используя @Autowired, и использовать его в своих тестах так же, как и при использовании Mock MVC и Spring REST Docs, как показано в следующем примере:

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(UserController.class)
@AutoConfigureRestDocs
class MyUserDocumentationTests {

    @Autowired
    private MockMvc mvc;

    @Test
    void listUsers() throws Exception {
        this.mvc.perform(get("/users").accept(MediaType.TEXT_PLAIN))
            .andExpect(status().isOk())
            .andDo(document("list-users"));
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.http.MediaType
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers

@WebMvcTest(UserController::class)
@AutoConfigureRestDocs
class MyUserDocumentationTests(@Autowired val mvc: MockMvc) {

    @Test
    fun listUsers() {
        mvc.perform(MockMvcRequestBuilders.get("/users").accept(MediaType.TEXT_PLAIN))
            .andExpect(MockMvcResultMatchers.status().isOk)
            .andDo(MockMvcRestDocumentation.document("list-users"))
    }

}

Если вам нужен больший контроль над конфигурацией Spring REST Docs, чем предоставляют атрибуты @AutoConfigureRestDocs, вы можете использовать бин RestDocsMockMvcConfigurationCustomizer, как показано в следующем примере:

Java
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationConfigurer;
import org.springframework.restdocs.templates.TemplateFormats;

@TestConfiguration(proxyBeanMethods = false)
public class MyRestDocsConfiguration implements RestDocsMockMvcConfigurationCustomizer {

    @Override
    public void customize(MockMvcRestDocumentationConfigurer configurer) {
        configurer.snippets().withTemplateFormat(TemplateFormats.markdown());
    }

}
Kotlin
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsMockMvcConfigurationCustomizer
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentationConfigurer
import org.springframework.restdocs.templates.TemplateFormats

@TestConfiguration(proxyBeanMethods = false)
class MyRestDocsConfiguration : RestDocsMockMvcConfigurationCustomizer {

    override fun customize(configurer: MockMvcRestDocumentationConfigurer) {
        configurer.snippets().withTemplateFormat(TemplateFormats.markdown())
    }

}

Если вам нужно использовать поддержку Spring REST Docs для параметризованной директории вывода, вы можете создать бин RestDocumentationResultHandler. Автоконфигурация вызывает alwaysDo с этим обработчиком результатов, тем самым заставляя каждый вызов MockMvc автоматически генерировать стандартные фрагменты. Следующий пример демонстрирует определение RestDocumentationResultHandler:

Java
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation;
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler;

@TestConfiguration(proxyBeanMethods = false)
public class MyResultHandlerConfiguration {

    @Bean
    public RestDocumentationResultHandler restDocumentation() {
        return MockMvcRestDocumentation.document("{method-name}");
    }

}
Kotlin
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation
import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler

@TestConfiguration(proxyBeanMethods = false)
class MyResultHandlerConfiguration {

    @Bean
    fun restDocumentation(): RestDocumentationResultHandler {
        return MockMvcRestDocumentation.document("{method-name}")
    }

}
Автонастроенные тесты Spring REST Docs с WebTestClient

@AutoConfigureRestDocs также можно использовать с WebTestClient при тестировании реактивных веб-приложений. Вы можете инжектировать его, используя @Autowired, и использовать его в тестах, как обычно при использовании @WebFluxTest и Spring REST Docs, как показано в следующем примере:

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
import org.springframework.test.web.reactive.server.WebTestClient;

import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document;

@WebFluxTest
@AutoConfigureRestDocs
class MyUsersDocumentationTests {

    @Autowired
    private WebTestClient webTestClient;

    @Test
    void listUsers() {
        this.webTestClient
            .get().uri("/")
        .exchange()
        .expectStatus()
            .isOk()
        .expectBody()
            .consumeWith(document("list-users"));
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs
import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest
import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation
import org.springframework.test.web.reactive.server.WebTestClient

@WebFluxTest
@AutoConfigureRestDocs
class MyUsersDocumentationTests(@Autowired val webTestClient: WebTestClient) {

    @Test
    fun listUsers() {
        webTestClient
            .get().uri("/")
            .exchange()
            .expectStatus()
            .isOk
            .expectBody()
            .consumeWith(WebTestClientRestDocumentation.document("list-users"))
    }

}

Если вам нужен больший контроль над конфигурацией Spring REST Docs, чем предоставляют атрибуты @AutoConfigureRestDocs, вы можете использовать бин RestDocsWebTestClientConfigurationCustomizer, как показано в следующем примере:

Java
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsWebTestClientConfigurationCustomizer;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentationConfigurer;

@TestConfiguration(proxyBeanMethods = false)
public class MyRestDocsConfiguration implements RestDocsWebTestClientConfigurationCustomizer {

    @Override
    public void customize(WebTestClientRestDocumentationConfigurer configurer) {
        configurer.snippets().withEncoding("UTF-8");
    }

}
Kotlin
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsWebTestClientConfigurationCustomizer
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentationConfigurer

@TestConfiguration(proxyBeanMethods = false)
class MyRestDocsConfiguration : RestDocsWebTestClientConfigurationCustomizer {

    override fun customize(configurer: WebTestClientRestDocumentationConfigurer) {
        configurer.snippets().withEncoding("UTF-8")
    }

}

Если вы хотите использовать поддержку Spring REST Docs для параметризованной директории вывода, вы можете использовать WebTestClientBuilderCustomizer для настройки обработчика для каждого результата обмена данными с сущностью. Следующий пример демонстрирует такое определение WebTestClientBuilderCustomizer:

Java
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.web.reactive.server.WebTestClientBuilderCustomizer;
import org.springframework.context.annotation.Bean;

import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document;

@TestConfiguration(proxyBeanMethods = false)
public class MyWebTestClientBuilderCustomizerConfiguration {

    @Bean
    public WebTestClientBuilderCustomizer restDocumentation() {
        return (builder) -> builder.entityExchangeResultConsumer(document("{method-name}"));
    }

}
Kotlin
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.boot.test.web.reactive.server.WebTestClientBuilderCustomizer
import org.springframework.context.annotation.Bean
import org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation
import org.springframework.test.web.reactive.server.WebTestClient

@TestConfiguration(proxyBeanMethods = false)
class MyWebTestClientBuilderCustomizerConfiguration {

    @Bean
    fun restDocumentation(): WebTestClientBuilderCustomizer {
        return WebTestClientBuilderCustomizer { builder: WebTestClient.Builder ->
            builder.entityExchangeResultConsumer(
                WebTestClientRestDocumentation.document("{method-name}")
            )
        }
    }

}
Автонастроенные тесты Spring REST Docs с REST Assured

@AutoConfigureRestDocs делает бин RequestSpecification, предварительно настроенный для использования Spring REST Docs, доступным для ваших тестов. Вы можете инжектировать его, используя @Autowired, и использовать его в тестах, как обычно при использовании REST Assured и Spring REST Docs, как показано в следующем примере:

Java
import io.restassured.specification.RequestSpecification;
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.server.LocalServerPort;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.is;
import static org.springframework.restdocs.restassured.RestAssuredRestDocumentation.document;

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureRestDocs
class MyUserDocumentationTests {

    @Test
    void listUsers(@Autowired RequestSpecification documentationSpec, @LocalServerPort int port) {
        given(documentationSpec)
            .filter(document("list-users"))
        .when()
            .port(port)
            .get("/")
        .then().assertThat()
            .statusCode(is(200));
    }

}
Kotlin
import io.restassured.RestAssured
import io.restassured.specification.RequestSpecification
import org.hamcrest.Matchers
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment
import org.springframework.boot.test.web.server.LocalServerPort
import org.springframework.restdocs.restassured.RestAssuredRestDocumentation

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureRestDocs
class MyUserDocumentationTests {

    @Test
    fun listUsers(@Autowired documentationSpec: RequestSpecification?, @LocalServerPort port: Int) {
        RestAssured.given(documentationSpec)
            .filter(RestAssuredRestDocumentation.document("list-users"))
            .`when`()
            .port(port)["/"]
            .then().assertThat()
            .statusCode(Matchers.`is`(200))
    }

}

Если вам нужен больший контроль над конфигурацией Spring REST Docs, чем предоставляют атрибуты @AutoConfigureRestDocs, можно использовать бин RestDocsRestAssuredConfigurationCustomizer, как показано в следующем примере:

Java
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsRestAssuredConfigurationCustomizer;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.restdocs.restassured.RestAssuredRestDocumentationConfigurer;
import org.springframework.restdocs.templates.TemplateFormats;

@TestConfiguration(proxyBeanMethods = false)
public class MyRestDocsConfiguration implements RestDocsRestAssuredConfigurationCustomizer {

    @Override
    public void customize(RestAssuredRestDocumentationConfigurer configurer) {
        configurer.snippets().withTemplateFormat(TemplateFormats.markdown());
    }

}
Kotlin
import org.springframework.boot.test.autoconfigure.restdocs.RestDocsRestAssuredConfigurationCustomizer
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.restdocs.restassured.RestAssuredRestDocumentationConfigurer
import org.springframework.restdocs.templates.TemplateFormats

@TestConfiguration(proxyBeanMethods = false)
class MyRestDocsConfiguration : RestDocsRestAssuredConfigurationCustomizer {

    override fun customize(configurer: RestAssuredRestDocumentationConfigurer) {
        configurer.snippets().withTemplateFormat(TemplateFormats.markdown())
    }

}

8.3.32. Автонастроенные тесты Spring Web Services

Автонастроенные тесты клиента Spring Web Services

Вы можете использовать @WebServiceClientTest для тестирования приложений, которые вызывают веб-сервисы с помощью проекта Spring Web Services. По умолчанию он настраивает моковый бин WebServiceServer и автоматически настраивает ваш WebServiceTemplateBuilder. (Дополнительную информацию об использовании веб-сервисов с Spring Boot см. в "io.html".)

Список настроек автоконфигурации, включённых @WebServiceClientTest, можно найти в приложении.

Следующий пример демонстрирует использование аннотации @WebServiceClientTest:

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.webservices.client.WebServiceClientTest;
import org.springframework.ws.test.client.MockWebServiceServer;
import org.springframework.xml.transform.StringSource;

import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.ws.test.client.RequestMatchers.payload;
import static org.springframework.ws.test.client.ResponseCreators.withPayload;

@WebServiceClientTest(SomeWebService.class)
class MyWebServiceClientTests {

    @Autowired
    private MockWebServiceServer server;

    @Autowired
    private SomeWebService someWebService;

    @Test
    void mockServerCall() {
        this.server
            .expect(payload(new StringSource("<request/>")))
            .andRespond(withPayload(new StringSource("<response><status>200</status></response>")));
        assertThat(this.someWebService.test())
            .extracting(Response::getStatus)
            .isEqualTo(200);
    }

}
Kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.webservices.client.WebServiceClientTest
import org.springframework.ws.test.client.MockWebServiceServer
import org.springframework.ws.test.client.RequestMatchers
import org.springframework.ws.test.client.ResponseCreators
import org.springframework.xml.transform.StringSource

@WebServiceClientTest(SomeWebService::class)
class MyWebServiceClientTests(@Autowired val server: MockWebServiceServer, @Autowired val someWebService: SomeWebService) {

    @Test
    fun mockServerCall() {
        server
            .expect(RequestMatchers.payload(StringSource("<request/>")))
            .andRespond(ResponseCreators.withPayload(StringSource("<response><status>200</status></response>")))
        assertThat(this.someWebService.test()).extracting(Response::status).isEqualTo(200)
    }

}
Автонастроенные тесты сервера Spring Web Services

Вы можете использовать @WebServiceServerTest для тестирования приложений, которые реализуют веб-сервисы с помощью проекта Spring Web Services. По умолчанию он настраивает бин MockWebServiceClient, который можно использовать для вызова ваших точек входа веб-сервиса. (Дополнительную информацию об использовании веб-сервисов с Spring Boot см. в "io.html".)

Список настроек автоконфигурации, включённых @WebServiceServerTest, можно найти в приложении.

Следующий пример демонстрирует использование аннотации @WebServiceServerTest:

Java
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.webservices.server.WebServiceServerTest;
import org.springframework.ws.test.server.MockWebServiceClient;
import org.springframework.ws.test.server.RequestCreators;
import org.springframework.ws.test.server.ResponseMatchers;
import org.springframework.xml.transform.StringSource;

@WebServiceServerTest(ExampleEndpoint.class)
class MyWebServiceServerTests {

    @Autowired
    private MockWebServiceClient client;

    @Test
    void mockServerCall() {
        this.client
            .sendRequest(RequestCreators.withPayload(new StringSource("<ExampleRequest/>")))
            .andExpect(ResponseMatchers.payload(new StringSource("<ExampleResponse>42</ExampleResponse>")));
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.webservices.server.WebServiceServerTest
import org.springframework.ws.test.server.MockWebServiceClient
import org.springframework.ws.test.server.RequestCreators
import org.springframework.ws.test.server.ResponseMatchers
import org.springframework.xml.transform.StringSource

@WebServiceServerTest(ExampleEndpoint::class)
class MyWebServiceServerTests(@Autowired val client: MockWebServiceClient) {

    @Test
    fun mockServerCall() {
        client
            .sendRequest(RequestCreators.withPayload(StringSource("<ExampleRequest/>")))
            .andExpect(ResponseMatchers.payload(StringSource("<ExampleResponse>42</ExampleResponse>")))
    }

}

8.3.33. Дополнительная автоконфигурация и нарезка

Каждый срез предоставляет одну или несколько аннотаций @AutoConfigure…​, которые, в частности, определяют автоконфигурации, которые должны быть включены в качестве части среза. Дополнительные автоконфигурации можно добавить в тесты по отдельности, создав пользовательскую аннотацию @AutoConfigure…​ или добавив @ImportAutoConfiguration в тест, как показано в следующем примере:

Java
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration;
import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest;

@JdbcTest
@ImportAutoConfiguration(IntegrationAutoConfiguration.class)
class MyJdbcTests {

}
Kotlin
import org.springframework.boot.autoconfigure.ImportAutoConfiguration
import org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration
import org.springframework.boot.test.autoconfigure.jdbc.JdbcTest

@JdbcTest
@ImportAutoConfiguration(IntegrationAutoConfiguration::class)
class MyJdbcTests
Убедитесь, что не используете обычную аннотацию @Import для импорта автоконфигураций, поскольку Spring Boot обрабатывает их особым образом.

В качестве альтернативы, дополнительные автоконфигурации можно добавить для любого использования аннотации среза, зарегистрировав их в файле, хранящемся в META-INF/spring, как показано в следующем примере:

META-INF/spring/org.springframework.boot.test.autoconfigure.jdbc.JdbcTest.imports
com.example.IntegrationAutoConfiguration

В этом примере com.example.IntegrationAutoConfiguration включена для каждого теста, помеченного аннотацией @JdbcTest.

Вы можете использовать комментарии с # в этом файле.
Срез или аннотация @AutoConfigure…​ можно настроить таким образом, если она мета-аннотирована @ImportAutoConfiguration.

8.3.34. Конфигурация пользователя и нарезка

Если вы структурируете свой код разумным образом, ваш класс @SpringBootApplication по умолчанию используется в качестве конфигурации ваших тестов.

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

Предположим, что вы используете Spring Data MongoDB, полагаетесь на автоматическую конфигурацию для него и включили аудит. Вы можете определить свой класс @SpringBootApplication следующим образом:

Java
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.mongodb.config.EnableMongoAuditing;

@SpringBootApplication
@EnableMongoAuditing
public class MyApplication {

    // ...

}
Kotlin
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.data.mongodb.config.EnableMongoAuditing

@SpringBootApplication
@EnableMongoAuditing
class MyApplication {

    // ...

}

Поскольку этот класс является исходной конфигурацией для теста, любой тестовый срез фактически пытается включить аудит Mongo, что определенно не то, что вам нужно. Рекомендуемый подход заключается в перемещении конфигурации, специфичной для области, в отдельный класс @Configuration на том же уровне, что и ваше приложение, как показано в следующем примере:

Java
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.EnableMongoAuditing;

@Configuration(proxyBeanMethods = false)
@EnableMongoAuditing
public class MyMongoConfiguration {

    // ...

}
Kotlin
import org.springframework.context.annotation.Configuration
import org.springframework.data.mongodb.config.EnableMongoAuditing;

@Configuration(proxyBeanMethods = false)
@EnableMongoAuditing
class MyMongoConfiguration {

    // ...

}
В зависимости от сложности вашего приложения, вы можете иметь либо один класс @Configuration для ваших настроек, либо по одному классу на область предметной области. Последний подход позволяет включить его в одном из ваших тестов, если необходимо, с помощью аннотации @Import. См. этот раздел руководства для получения дополнительных сведений о том, когда вам может потребоваться включить определенные классы @Configuration для тестовых срезов.

Тестовые срезы исключают классы @Configuration из сканирования. Например, для @WebMvcTest следующая конфигурация не будет включать заданный бин WebMvcConfigurer в контекст приложения, загруженный тестовым срезом:

Java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration(proxyBeanMethods = false)
public class MyWebConfiguration {

    @Bean
    public WebMvcConfigurer testConfigurer() {
        return new WebMvcConfigurer() {
            // ...
        };
    }

}
Kotlin
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer

@Configuration(proxyBeanMethods = false)
class MyWebConfiguration {

    @Bean
    fun testConfigurer(): WebMvcConfigurer {
        return object : WebMvcConfigurer {
            // ...
        }
    }

}

Однако, конфигурация ниже приведет к загрузке настраиваемого WebMvcConfigurer тестовым срезом.

Java
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Component
public class MyWebMvcConfigurer implements WebMvcConfigurer {

    // ...

}
Kotlin
import org.springframework.stereotype.Component
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer

@Component
class MyWebMvcConfigurer : WebMvcConfigurer {

    // ...

}

Еще одним источником путаницы является сканирование classpath. Предположим, что, хотя вы структурировали свой код разумным образом, вам нужно просканировать дополнительный пакет. Ваше приложение может иметь следующий код:

Java
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan({ "com.example.app", "com.example.another" })
public class MyApplication {

    // ...

}
Kotlin
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.annotation.ComponentScan

@SpringBootApplication
@ComponentScan("com.example.app", "com.example.another")
class MyApplication {

    // ...

}

Это фактически переопределяет стандартную директиву сканирования компонентов с побочным эффектом сканирования этих двух пакетов независимо от выбранного среза. Например, тестовый срез может внезапно начать сканировать компоненты и пользовательские конфигурации вашего приложения. Опять же, перемещение пользовательской директивы в отдельный класс — хороший способ исправить эту проблему.

Если это для вас не вариант, вы можете создать @SpringBootConfiguration где-то в иерархии вашего теста, чтобы он использовался вместо него. В качестве альтернативы, вы можете указать источник для своего теста, что отключит поведение поиска по умолчанию.

8.3.35. Использование Spock для тестирования приложений Spring Boot

Spock 2.2 или более поздней версии можно использовать для тестирования приложения Spring Boot. Для этого добавьте зависимость от версии -groovy-4.0 модуля spock-spring Spock в ваш проект. spock-spring интегрирует фреймворк тестов Spring в Spock. Дополнительные сведения см. в документации модуля Spring для Spock.

8.4. Тестконтейнеры

Библиотека Testcontainers предоставляет способ управления службами, работающими внутри контейнеров Docker. Она интегрируется с JUnit, позволяя вам создавать класс тестов, который может запускать контейнер перед выполнением любых тестов. Testcontainers особенно полезна для написания интеграционных тестов, которые взаимодействуют с реальной службой бэкенда, такой как MySQL, MongoDB, Cassandra и другие.

Testcontainers можно использовать в тесте Spring Boot следующим образом:

Java
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import org.springframework.boot.test.context.SpringBootTest;

@Testcontainers
@SpringBootTest
class MyIntegrationTests {

    @Container
    static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5");

    @Test
    void myTest() {
        // ...
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.testcontainers.containers.Neo4jContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers

@Testcontainers
@SpringBootTest
class MyIntegrationTests {

    @Test
    fun myTest() {
        // ...
    }

    companion object {
        @Container
        val neo4j = Neo4jContainer("neo4j:5")
    }

}

Это запустит контейнер Docker с Neo4j (если Docker запущен локально) перед запуском любых тестов. В большинстве случаев необходимо настроить приложение для подключения к службе, работающей в контейнере.

8.4.1. Подключения к сервисам

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

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

Java
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;

@Testcontainers
@SpringBootTest
class MyIntegrationTests {

    @Container
    @ServiceConnection
    static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5");

    @Test
    void myTest() {
        // ...
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.testcontainers.service.connection.ServiceConnection
import org.testcontainers.containers.Neo4jContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers

@Testcontainers
@SpringBootTest
class MyIntegrationTests {

    @Test
    fun myTest() {
        // ...
    }

    companion object {

        @Container
        @ServiceConnection
        val neo4j = Neo4jContainer("neo4j:5")

    }

}

Благодаря @ServiceConnection, вышеуказанная настройка позволяет связанным с Neo4j компонентам приложения взаимодействовать с Neo4j, работающим внутри управляемого Testcontainers Docker контейнера. Это делается путем автоматического определения компонента Neo4jConnectionDetails, который затем используется автоконфигурацией Neo4j, переопределяя любые свойства конфигурации, связанные с подключением.

Вам необходимо добавить модуль spring-boot-testcontainers в качестве тестовой зависимости, чтобы использовать подключения к сервисам с Testcontainers.

Аннотации подключений к сервисам обрабатываются классами ContainerConnectionDetailsFactory, зарегистрированными с помощью spring.factories. Класс ContainerConnectionDetailsFactory может создавать компонент ConnectionDetails на основе конкретного подкласса Container или имени образа Docker.

В JAR-файле spring-boot-testcontainers предоставляются следующие фабрики подключений к сервисам:

Детали подключения Сопоставлено по

CassandraConnectionDetails

Контейнеры типа CassandraContainer

CouchbaseConnectionDetails

Контейнеры типа CouchbaseContainer

По умолчанию все соответствующие компоненты подключения будут созданы для данного Container. Например, PostgreSQLContainer создаст как JdbcConnectionDetails, так и R2dbcConnectionDetails.

Если требуется создать только подмножество применимых типов, можно использовать атрибут type компонента @ServiceConnection.

По умолчанию используется Container.getDockerImageName() для получения имени, используемого для поиска данных подключения. При использовании пользовательского образа Docker можно использовать атрибут name компонента @ServiceConnection для его переопределения.

Например, если у вас есть GenericContainer, использующий образ Docker registry.mycompany.com/mirror/myredis, вам нужно использовать @ServiceConnection(name="redis"), чтобы гарантировать создание RedisConnectionDetails.

8.4.2. Динамические свойства

Несколько более подробный, но и более гибкий вариант подключения к сервисам — это @DynamicPropertySource. Статический метод @DynamicPropertySource позволяет добавлять динамические значения свойств в среду Spring.

Java
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;

@Testcontainers
@SpringBootTest
class MyIntegrationTests {

    @Container
    static Neo4jContainer<?> neo4j = new Neo4jContainer<>("neo4j:5");

    @Test
    void myTest() {
        // ...
    }

    @DynamicPropertySource
    static void neo4jProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.neo4j.uri", neo4j::getBoltUrl);
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.DynamicPropertyRegistry
import org.springframework.test.context.DynamicPropertySource
import org.testcontainers.containers.Neo4jContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers

@Testcontainers
@SpringBootTest
class MyIntegrationTests {

    @Test
    fun myTest() {
        // ...
    }

    companion object {

        @Container
        val neo4j = Neo4jContainer("neo4j:5")

        @DynamicPropertySource
        fun neo4jProperties(registry: DynamicPropertyRegistry) {
            registry.add("spring.neo4j.uri") { neo4j.boltUrl }
        }

    }

}

Вышеуказанная конфигурация позволяет связанным с Neo4j компонентам приложения взаимодействовать с Neo4j, работающим внутри управляемого Testcontainers Docker контейнера.

8.4.3. Использование Testcontainers на этапе разработки

Помимо использования Testcontainers для интеграционных тестов, их также можно применять на этапе разработки. Такой подход позволяет разработчикам быстро запускать контейнеры для служб, от которых зависит приложение, исключая необходимость ручного развёртывания, например, баз данных. Использование Testcontainers таким образом обеспечивает функциональность, аналогичную Docker Compose, за исключением того, что конфигурация контейнера задаётся на Java, а не в YAML.

Чтобы использовать Testcontainers на этапе разработки, необходимо запустить ваше приложение, используя «тестовый» класспаф, а не «основной». Это позволит вам получить доступ ко всем объявленным тестовым зависимостям и предоставит естественное место для написания тестовой конфигурации.

Для создания запускаемого в тестовом режиме варианта вашего приложения, необходимо создать класс «Application» в каталоге src/test. Например, если ваше основное приложение находится в src/main/java/com/example/MyApplication.java, то вы должны создать src/test/java/com/example/TestMyApplication.java

Класс TestMyApplication может использовать метод SpringApplication.from(…​) для запуска реального приложения:

Java
import org.springframework.boot.SpringApplication;

public class TestMyApplication {

    public static void main(String[] args) {
        SpringApplication.from(MyApplication::main).run(args);
    }

}
Kotlin
import org.springframework.boot.fromApplication

fun main(args: Array<String>) {
    fromApplication<MyApplication>().run(*args)
}

Вам также необходимо определить экземпляры Container, которые вы хотите запустить вместе с приложением. Для этого нужно убедиться, что модуль spring-boot-testcontainers добавлен в качестве зависимости test. После этого вы можете создать класс @TestConfiguration, который объявляет методы @Bean для контейнеров, которые вы хотите запустить.

Вы также можете аннотировать методы @Bean с помощью @ServiceConnection для создания ConnectionDetails бинов. Подробности поддерживаемых технологий см. в разделе соединений с сервисами выше.

Типичная конфигурация Testcontainers будет выглядеть так:

Java
import org.testcontainers.containers.Neo4jContainer;

import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.springframework.context.annotation.Bean;

@TestConfiguration(proxyBeanMethods = false)
public class MyContainersConfiguration {

    @Bean
    @ServiceConnection
    public Neo4jContainer<?> neo4jContainer() {
        return new Neo4jContainer<>("neo4j:5");
    }

}
Kotlin
import org.testcontainers.containers.Neo4jContainer

import org.springframework.boot.test.context.TestConfiguration
import org.springframework.boot.testcontainers.service.connection.ServiceConnection
import org.springframework.context.annotation.Bean;

@TestConfiguration(proxyBeanMethods = false)
class MyContainersConfiguration {

    @Bean
    @ServiceConnection
    fun neo4jContainer(): Neo4jContainer<*> {
        return Neo4jContainer("neo4j:5")
    }

}
Жизненный цикл Container бинов автоматически управляется Spring Boot. Контейнеры будут автоматически запускаться и останавливаться.

После определения конфигурации тестов, вы можете использовать метод with(…​) для подключения её к запуску ваших тестов:

Java
import org.springframework.boot.SpringApplication;

public class TestMyApplication {

    public static void main(String[] args) {
        SpringApplication.from(MyApplication::main).with(MyContainersConfiguration.class).run(args);
    }

}
Kotlin
import org.springframework.boot.fromApplication
import org.springframework.boot.with

fun main(args: Array<String>) {
    fromApplication<MyApplication>().with(MyContainersConfiguration::class).run(*args)
}

Теперь вы можете запустить TestMyApplication как любой обычный Java main метод для запуска вашего приложения и контейнеров, необходимых для его работы.

Вы можете использовать Maven цель spring-boot:test-run или Gradle задачу bootTestRun для этого из командной строки.
Внесение динамических свойств во время разработки

Если вы хотите внести динамические свойства на этапе разработки из ваших Container @Bean методов, вы можете сделать это, введя DynamicPropertyRegistry. Это работает аналогично @DynamicPropertySource аннотации, которую можно использовать в ваших тестах. Это позволяет добавлять свойства, которые станут доступны после запуска вашего контейнера.

Типичная конфигурация будет выглядеть так:

Java
import org.testcontainers.containers.MongoDBContainer;

import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.DynamicPropertyRegistry;

@TestConfiguration(proxyBeanMethods = false)
public class MyContainersConfiguration {

    @Bean
    public MongoDBContainer mongoDbContainer(DynamicPropertyRegistry properties) {
        MongoDBContainer container = new MongoDBContainer("mongo:5.0");
        properties.add("spring.data.mongodb.host", container::getHost);
        properties.add("spring.data.mongodb.port", container::getFirstMappedPort);
        return container;
    }

}
Kotlin
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.DynamicPropertyRegistry
import org.testcontainers.containers.MongoDBContainer

@TestConfiguration(proxyBeanMethods = false)
class MyContainersConfiguration {

    @Bean
    fun monogDbContainer(properties: DynamicPropertyRegistry): MongoDBContainer {
        var container = MongoDBContainer("mongo:5.0")
        properties.add("spring.data.mongodb.host", container::getHost);
        properties.add("spring.data.mongodb.port", container::getFirstMappedPort);
        return container
    }

}
Использование @ServiceConnection рекомендуется всегда, когда это возможно, однако динамические свойства могут быть полезной заменой для технологий, которые пока не поддерживают @ServiceConnection.
Импортирование классов объявлений контейнеров

Распространённый шаблон при использовании Testcontainers — объявлять экземпляры Container как статические поля. Часто эти поля объявляются непосредственно в классе теста. Их также можно объявить в родительском классе или в интерфейсе, который реализует тест.

Например, следующий интерфейс MyContainers объявляет контейнеры mongo и neo4j:

import org.testcontainers.containers.MongoDBContainer;
import org.testcontainers.containers.Neo4jContainer;
import org.testcontainers.junit.jupiter.Container;

public interface MyContainers {

    @Container
    MongoDBContainer mongoContainer = new MongoDBContainer("mongo:5.0");

    @Container
    Neo4jContainer<?> neo4jContainer = new Neo4jContainer<>("neo4j:5");

}

Если у вас уже есть контейнеры, определённые таким образом, или вы просто предпочитаете этот стиль, вы можете импортировать эти классы объявлений, вместо определения ваших контейнеров как @Bean методов. Для этого добавьте аннотацию @ImportTestcontainers к вашему классу конфигурации тестов:

Java
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.testcontainers.context.ImportTestcontainers;

@TestConfiguration(proxyBeanMethods = false)
@ImportTestcontainers(MyContainers.class)
public class MyContainersConfiguration {

}
Kotlin
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.boot.testcontainers.context.ImportTestcontainers

@TestConfiguration(proxyBeanMethods = false)
@ImportTestcontainers(MyContainers::class)
class MyContainersConfiguration {

}
Вы можете использовать аннотацию @ServiceConnection на Container полях для установления соединений с сервисами. Вы также можете добавить @DynamicPropertySource аннотированные методы в ваш класс объявления.
Использование DevTools с Testcontainers на этапе разработки

При использовании devtools вы можете аннотировать бины и методы бинов с помощью @RestartScope. Такие бины не будут пересозданы при перезапуске приложения devtools. Это особенно полезно для Testcontainer Container бинов, так как они сохраняют своё состояние, несмотря на перезапуск приложения.

Java
import org.testcontainers.containers.MongoDBContainer;

import org.springframework.boot.devtools.restart.RestartScope;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;

@TestConfiguration(proxyBeanMethods = false)
public class MyContainersConfiguration {

    @Bean
    @RestartScope
    public MongoDBContainer mongoDbContainer() {
        return new MongoDBContainer("mongo:5.0");
    }

}
Kotlin
import org.springframework.boot.devtools.restart.RestartScope
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.testcontainers.containers.MongoDBContainer

@TestConfiguration(proxyBeanMethods = false)
class MyContainersConfiguration {

    @Bean
    @RestartScope
    fun monogDbContainer(): MongoDBContainer {
        return MongoDBContainer("mongo:5.0")
    }

}
Если вы используете Gradle и хотите использовать эту функцию, вам нужно изменить конфигурацию зависимости spring-boot-devtools со значения developmentOnly на testImplementation. При стандартном scope developmentOnly, задача bootTestRun не будет подхватывать изменения в вашем коде, так как devtools не активны.

8.5. Инструменты для тестирования

Несколько классов-инструментов для тестирования, которые обычно полезны при тестировании вашего приложения, упакованы в составе spring-boot.

8.5.1. ConfigDataApplicationContextInitializer

ConfigDataApplicationContextInitializer — это ApplicationContextInitializer, который вы можете применить к вашим тестам для загрузки файлов конфигурации Spring Boot application.properties. Вы можете использовать его, когда вам не нужен полный набор функций, предоставляемых @SpringBootTest, как показано в следующем примере:

Java
import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer;
import org.springframework.test.context.ContextConfiguration;

@ContextConfiguration(classes = Config.class, initializers = ConfigDataApplicationContextInitializer.class)
class MyConfigFileTests {

    // ...

}
Kotlin
import org.springframework.boot.test.context.ConfigDataApplicationContextInitializer
import org.springframework.test.context.ContextConfiguration

@ContextConfiguration(classes = [Config::class], initializers = [ConfigDataApplicationContextInitializer::class])
class MyConfigFileTests {

    // ...

}
Использование только ConfigDataApplicationContextInitializer не обеспечивает поддержку инъекции @Value("${…​}"). Его единственная задача — обеспечить загрузку файлов конфигурации application.properties в контекст Spring Environment. Для поддержки @Value, вам необходимо либо дополнительно настроить PropertySourcesPlaceholderConfigurer, либо использовать @SpringBootTest, который автоматически настраивает его.

8.5.2. TestPropertyValues

TestPropertyValues позволяет быстро добавлять свойства в ConfigurableEnvironment или ConfigurableApplicationContext. Вы можете вызвать его со строками key=value, как показано ниже:

Java
import org.junit.jupiter.api.Test;

import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.mock.env.MockEnvironment;

import static org.assertj.core.api.Assertions.assertThat;

class MyEnvironmentTests {

    @Test
    void testPropertySources() {
        MockEnvironment environment = new MockEnvironment();
        TestPropertyValues.of("org=Spring", "name=Boot").applyTo(environment);
        assertThat(environment.getProperty("name")).isEqualTo("Boot");
    }

}
Kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.boot.test.util.TestPropertyValues
import org.springframework.mock.env.MockEnvironment

class MyEnvironmentTests {

    @Test
    fun testPropertySources() {
        val environment = MockEnvironment()
        TestPropertyValues.of("org=Spring", "name=Boot").applyTo(environment)
        assertThat(environment.getProperty("name")).isEqualTo("Boot")
    }

}

8.5.3. OutputCapture

OutputCapture — это JUnit Extension, которое можно использовать для захвата System.out и System.err вывода. Для использования его добавьте @ExtendWith(OutputCaptureExtension.class) и введите CapturedOutput в качестве аргумента конструктора класса теста или метода теста следующим образом:

Java
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import org.springframework.boot.test.system.CapturedOutput;
import org.springframework.boot.test.system.OutputCaptureExtension;

import static org.assertj.core.api.Assertions.assertThat;

@ExtendWith(OutputCaptureExtension.class)
class MyOutputCaptureTests {

    @Test
    void testName(CapturedOutput output) {
        System.out.println("Hello World!");
        assertThat(output).contains("World");
    }

}
Kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.springframework.boot.test.system.CapturedOutput
import org.springframework.boot.test.system.OutputCaptureExtension

@ExtendWith(OutputCaptureExtension::class)
class MyOutputCaptureTests {

    @Test
    fun testName(output: CapturedOutput?) {
        println("Hello World!")
        assertThat(output).contains("World")
    }

}

8.5.4. TestRestTemplate

TestRestTemplate — это удобная альтернатива Spring’s RestTemplate, которая полезна в интеграционных тестах. Вы можете получить шаблон без настроек или шаблон, который отправляет аутентификацию Basic HTTP (с именем пользователя и паролем). В любом случае, шаблон устойчив к ошибкам. Это означает, что он ведет себя дружелюбно по отношению к тестам, не выбрасывая исключений при 4xx и 5xx ошибках. Вместо этого такие ошибки можно обнаружить с помощью возвращаемого ResponseEntity и его кода состояния.

Spring Framework 5.0 предоставляет новый WebTestClient, который работает для тестов интеграции WebFlux и для тестирования WebFlux и MVC "от конца до конца". Он предоставляет удобочитаемый API для утверждений, в отличие от TestRestTemplate.

Рекомендуется, но не обязательно, использовать Apache HTTP Client (версия 5.1 или выше). Если он есть в вашем классе, TestRestTemplate отреагирует, правильно настроив клиент. Если вы используете Apache HTTP клиент, некоторые дополнительные функции, дружественные для тестирования, будут включены:

  • Перенаправлений не происходит (чтобы вы могли утверждать расположение ответа).

  • Куки игнорируются (чтобы шаблон был бессостоятельным).

TestRestTemplate можно создать напрямую в интеграционных тестах, как показано в следующем примере:

Java
import org.junit.jupiter.api.Test;

import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.ResponseEntity;

import static org.assertj.core.api.Assertions.assertThat;

class MyTests {

    private final TestRestTemplate template = new TestRestTemplate();

    @Test
    void testRequest() {
        ResponseEntity<String> headers = this.template.getForEntity("https://myhost.example.com/example", String.class);
        assertThat(headers.getHeaders().getLocation()).hasHost("other.example.com");
    }

}
Kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.boot.test.web.client.TestRestTemplate

class MyTests {

    private val template = TestRestTemplate()

    @Test
    fun testRequest() {
        val headers = template.getForEntity("https://myhost.example.com/example", String::class.java)
        assertThat(headers.headers.location).hasHost("other.example.com")
    }

}

В качестве альтернативы, если вы используете аннотацию @SpringBootTest с WebEnvironment.RANDOM_PORT или WebEnvironment.DEFINED_PORT, вы можете внедрить полностью настроенный TestRestTemplate и начать использовать его. При необходимости дополнительные настройки можно применить через бин RestTemplateBuilder. Любые URL-адреса, которые не указывают хост и порт, автоматически подключаются к встраиваемому серверу, как показано в следующем примере:

Java
import java.time.Duration;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MySpringBootTests {

    @Autowired
    private TestRestTemplate template;

    @Test
    void testRequest() {
        HttpHeaders headers = this.template.getForEntity("/example", String.class).getHeaders();
        assertThat(headers.getLocation()).hasHost("other.example.com");
    }

    @TestConfiguration(proxyBeanMethods = false)
    static class RestTemplateBuilderConfiguration {

        @Bean
        RestTemplateBuilder restTemplateBuilder() {
            return new RestTemplateBuilder().setConnectTimeout(Duration.ofSeconds(1))
                .setReadTimeout(Duration.ofSeconds(1));
        }

    }

}
Kotlin
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.boot.test.web.client.TestRestTemplate
import org.springframework.boot.web.client.RestTemplateBuilder
import org.springframework.context.annotation.Bean
import java.time.Duration

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class MySpringBootTests(@Autowired val template: TestRestTemplate) {

    @Test
    fun testRequest() {
        val headers = template.getForEntity("/example", String::class.java).headers
        assertThat(headers.location).hasHost("other.example.com")
    }

    @TestConfiguration(proxyBeanMethods = false)
    internal class RestTemplateBuilderConfiguration {

        @Bean
        fun restTemplateBuilder(): RestTemplateBuilder {
            return RestTemplateBuilder().setConnectTimeout(Duration.ofSeconds(1))
                .setReadTimeout(Duration.ofSeconds(1))
        }

    }

}

9. Поддержка Docker Compose

Docker Compose — популярная технология, позволяющая определять и управлять несколькими контейнерами для служб, необходимых вашему приложению. Обычно создается файл compose.yml рядом с вашим приложением, который определяет и настраивает контейнеры служб.

Типичный рабочий процесс с Docker Compose заключается в запуске docker compose up, работе над вашим приложением с подключением к запущенным службам, а затем запуске docker compose down по завершении работы.

Модуль spring-boot-docker-compose можно включить в проект для поддержки работы с контейнерами с использованием Docker Compose. Добавьте зависимость модуля в свой билдер, как показано в следующих примерах для Maven и Gradle:

Maven
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-docker-compose</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>
Gradle
dependencies {
    developmentOnly("org.springframework.boot:spring-boot-docker-compose")
}

При включении этого модуля в качестве зависимости Spring Boot выполнит следующие действия:

  • Поиск файла compose.yml и других распространенных файлов compose в каталоге вашего приложения

  • Вызов docker compose up с обнаруженным файлом compose.yml

  • Создание бинов соединений со службой для каждого поддерживаемого контейнера

  • Вызов docker compose stop при завершении работы приложения

Для корректной работы поддержки Spring Boot приложение docker compose или docker-compose CLI должно быть доступно в вашей системе.
По умолчанию поддержка Docker Compose в Spring Boot отключена при запуске тестов. Чтобы включить её, установите spring.docker.compose.skip.in-tests в значение false.

9.1. Соединения со службами

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

При использовании поддержки Docker Compose в Spring Boot устанавливаются соединения со службой к порту, сопоставленному с контейнером.

Docker Compose обычно используется таким образом, что порты внутри контейнера отображаются на временные порты на вашем компьютере. Например, сервер Postgres может работать внутри контейнера по порту 5432, но быть сопоставленным с совершенно другим портом локально. Соединение со службой всегда обнаружит и будет использовать сопоставленный локальный порт.

Соединения со службами устанавливаются с помощью имени образа контейнера. В настоящее время поддерживаются следующие соединения со службами:

Подробности соединения Соответствие

CassandraConnectionDetails

Контейнеры с именем «cassandra»

ElasticsearchConnectionDetails

Контейнеры с именем «elasticsearch»

JdbcConnectionDetails

Контейнеры с именем «gvenzl/oracle-xe», «mariadb», «mssql/server», «mysql» или «postgres»

MongoConnectionDetails

Контейнеры с именем «mongo»

R2dbcConnectionDetails

Контейнеры с именем «gvenzl/oracle-xe», «mariadb», «mssql/server», «mysql» или «postgres»

RabbitConnectionDetails

Контейнеры с именем «rabbitmq»

RedisConnectionDetails

Контейнеры с именем «redis»

ZipkinConnectionDetails

Контейнеры с именем «openzipkin/zipkin».

9.2. Пользовательские образы

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

Если ваш образ использует другое имя, вы можете использовать метку в файле compose.yml, чтобы Spring Boot мог предоставить соединение со службой. Используйте метку с именем org.springframework.boot.service-connection для предоставления имени службы.

Например:

services:
  redis:
    image: 'mycompany/mycustomredis:7.0'
    ports:
      - '6379'
    labels:
      org.springframework.boot.service-connection: redis

9.3. Пропуск определенных контейнеров

Если в вашем файле compose.yml определен образ контейнера, который вы не хотите подключать к своему приложению, вы можете использовать метку для его игнорирования. Любой контейнер с меткой org.springframework.boot.ignore будет пропущен Spring Boot.

Например:

services:
  redis:
    image: 'redis:7.0'
    ports:
      - '6379'
    labels:
      org.springframework.boot.ignore: true

9.4. Использование определенного файла Compose

Если файл compose находится не в той же директории, что и ваше приложение, или имеет другое имя, вы можете использовать spring.docker.compose.file в своих файлах конфигурации application.properties или application.yaml, чтобы указать на другой файл. Свойства могут быть определены как точный путь или путь, относительный к вашему приложению.

Например:

Свойства
spring.docker.compose.file=../my-compose.yml
Yaml
spring:
  docker:
    compose:
      file: "../my-compose.yml"

9.5. Ожидание готовности контейнера

Контейнеры, запущенные с помощью Docker Compose, могут некоторое время занимать готовность. Рекомендуемый способ проверки готовности — добавление раздела healthcheck в определение службы в вашем файле compose.yml.

Поскольку нередко конфигурация healthcheck пропускается из файлов compose.yml, Spring Boot также проверяет готовность службы напрямую. По умолчанию контейнер считается готовым, когда можно установить TCP/IP-соединение с его сопоставленным портом.

Вы можете отключить это на основе контейнера, добавив метку org.springframework.boot.readiness-check.tcp.disable в ваш файл compose.yml.

Например:

services:
  redis:
    image: 'redis:7.0'
    ports:
      - '6379'
    labels:
      org.springframework.boot.readiness-check.tcp.disable: true

Вы также можете изменить значения тайм-аута в файле application.properties или application.yaml:

Свойства
spring.docker.compose.readiness.tcp.connect-timeout=10s
spring.docker.compose.readiness.tcp.read-timeout=5s
Yaml
spring:
  docker:
    compose:
      readiness:
        tcp:
          connect-timeout: 10s
          read-timeout: 5s

Общий тайм-аут можно настроить с помощью spring.docker.compose.readiness.timeout.

9.6. Управление жизненным циклом Docker Compose

По умолчанию Spring Boot вызывает docker compose up при запуске приложения и docker compose stop при его завершении. Если вы предпочитаете другой способ управления жизненным циклом, вы можете использовать свойство spring.docker.compose.lifecycle-management.

Поддерживаются следующие значения:

  • none — не запускать и не останавливать Docker Compose

  • start-only — запустить Docker Compose при запуске приложения и оставить его работающим

  • start-and-stop — запустить Docker Compose при запуске приложения и остановить его при выходе JVM

Кроме того, вы можете использовать свойство spring.docker.compose.start.command, чтобы изменить использование docker compose up или docker compose start. Свойство spring.docker.compose.stop.command позволяет настроить использование docker compose down или docker compose stop.

Следующий пример демонстрирует настройку управления жизненным циклом:

Свойства
spring.docker.compose.lifecycle-management=start-and-stop
spring.docker.compose.start.command=start
spring.docker.compose.stop.command=down
spring.docker.compose.stop.timeout=1m
Yaml
spring:
  docker:
    compose:
      lifecycle-management: start-and-stop
      start:
        command: start
      stop:
        command: down
        timeout: 1m

9.7. Активация профилей Docker Compose

Профили Docker Compose аналогичны профилям Spring, позволяя настраивать конфигурацию Docker Compose для определенных сред. Для активации определенного профиля Docker Compose можно использовать свойство spring.docker.compose.profiles.active в файлах application.properties или application.yaml:

Свойства
spring.docker.compose.profiles.active=myprofile
Yaml
spring:
  docker:
    compose:
      profiles:
        active: "myprofile"

10. Создание собственной автоконфигурации

Если вы работаете в компании, которая разрабатывает общие библиотеки, или если вы работаете над открытым исходным кодом или коммерческой библиотекой, вам может потребоваться разработать собственную автоконфигурацию. Классы автоконфигурации могут быть объединены во внешних JAR-файлах и все равно будут подбираться Spring Boot.

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

10.1. Понимание автоконфигурируемых бинов

Классы, реализующие автоконфигурацию, аннотированы с помощью @AutoConfiguration. Эта аннотация сама по себе мета-аннотирована с помощью @Configuration, делая автоконфигурации стандартными @Configuration классами. Дополнительные @Conditional аннотации используются для ограничения того, когда должна применяться автоконфигурация. Обычно классы автоконфигурации используют @ConditionalOnClass и @ConditionalOnMissingBean аннотации. Это гарантирует, что автоконфигурация применяется только тогда, когда найдены соответствующие классы и когда вы не объявили собственные @Configuration.

Вы можете просмотреть исходный код spring-boot-autoconfigure, чтобы увидеть @AutoConfiguration классы, предоставляемые Spring (см. META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports файл).

10.2. Поиск кандидатов на автоконфигурацию

Spring Boot проверяет наличие файла META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports в вашем опубликованном JAR-файле. Файл должен содержать список ваших конфигурационных классов, по одному имени класса в строке, как показано в следующем примере:

com.mycorp.libx.autoconfigure.LibXAutoConfiguration
com.mycorp.libx.autoconfigure.LibXWebAutoConfiguration
Вы можете добавлять комментарии к файлу импортов, используя символ #.
Автоконфигурации должны загружаться только путем указания их имен в файле импортов. Убедитесь, что они определены в конкретном пространстве пакетов и что они никогда не являются целевыми для сканирования компонентов. Кроме того, классы автоконфигурации не должны активировать сканирование компонентов для поиска дополнительных компонентов. Вместо этого следует использовать специальные аннотации @Import.

Если ваша конфигурация должна применяться в определенном порядке, вы можете использовать атрибуты before, beforeName, after и afterName в аннотации @AutoConfiguration или специальные аннотации @AutoConfigureBefore и @AutoConfigureAfter. Например, если вы предоставляете конфигурацию, специфичную для веб-приложений, ваш класс может потребоваться применить после WebMvcAutoConfiguration.

Если вам нужно упорядочить определенные автоконфигурации, которые не должны иметь прямого знания друг о друге, вы также можете использовать @AutoConfigureOrder. Эта аннотация имеет тот же смысл, что и обычная аннотация @Order, но предоставляет специальный порядок для классов автоконфигурации.

Как и в случае со стандартными @Configuration классами, порядок применения классов автоконфигурации влияет только на порядок определения их бинов. Порядок, в котором эти бины создаются впоследствии, не затрагивается и определяется зависимостями каждого бина и любыми отношениями @DependsOn.

10.3. Условные аннотации

Практически всегда необходимо включать одну или несколько аннотаций @Conditional в вашем классе автоконфигурации. Аннотация @ConditionalOnMissingBean — это распространённый пример, используемый для того, чтобы разработчики могли переопределять автоконфигурацию, если их не устраивают ваши значения по умолчанию.

Spring Boot включает ряд аннотаций @Conditional, которые можно повторно использовать в собственном коде, аннотируя классы @Configuration или отдельные методы @Bean. Эти аннотации включают:

  • Условные аннотации для классов

  • Условные аннотации для бинов

  • Условные аннотации для свойств

  • Условные аннотации для ресурсов

  • Условные аннотации для веб-приложений

  • Условные аннотации для выражений SpEL

10.3.1. Условные аннотации для классов

Аннотации @ConditionalOnClass и @ConditionalOnMissingClass позволяют включать классы @Configuration на основе наличия или отсутствия определённых классов. Поскольку метаданные аннотаций анализируются с помощью ASM, вы можете использовать атрибут value для ссылки на реальный класс, даже если этот класс фактически не присутствует в загружаемом приложении. Также можно использовать атрибут name, если предпочитаете указывать имя класса, используя значение String.

Этот механизм не применяется к методам @Bean таким же образом, так как в типичном случае возвращаемый тип является целью условия: перед применением условия к методу JVM загружает класс и потенциально обрабатывает ссылки на методы, что может привести к ошибке, если класса нет.

Для обработки этой ситуации можно использовать отдельный класс @Configuration, чтобы изолировать условие, как показано в следующем примере:

Java
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@AutoConfiguration
// Some conditions ...
public class MyAutoConfiguration {

    // Auto-configured beans ...

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(SomeService.class)
    public static class SomeServiceConfiguration {

        @Bean
        @ConditionalOnMissingBean
        public SomeService someService() {
            return new SomeService();
        }

    }

}
Kotlin
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
// Some conditions ...
class MyAutoConfiguration {

    // Auto-configured beans ...
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(SomeService::class)
    class SomeServiceConfiguration {

        @Bean
        @ConditionalOnMissingBean
        fun someService(): SomeService {
            return SomeService()
        }

    }

}
Если вы используете @ConditionalOnClass или @ConditionalOnMissingClass в качестве части мета-аннотации для создания собственных составных аннотаций, вы должны использовать name, так как в этом случае ссылка на класс не обрабатывается.

10.3.2. Условные аннотации для бинов

Аннотации @ConditionalOnBean и @ConditionalOnMissingBean позволяют включать бины на основе наличия или отсутствия определённых бинов. Вы можете использовать атрибут value для указания бинов по типу или name для указания бинов по имени. Атрибут search позволяет ограничить иерархию ApplicationContext, которую следует учитывать при поиске бинов.

При размещении на методе @Bean целевой тип по умолчанию — возвращаемый тип метода, как показано в следующем примере:

Java
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;

@AutoConfiguration
public class MyAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public SomeService someService() {
        return new SomeService();
    }

}
Kotlin
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration

@Configuration(proxyBeanMethods = false)
class MyAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    fun someService(): SomeService {
        return SomeService()
    }

}

В приведенном примере бин someService будет создан, если бин типа SomeService ещё не содержится в ApplicationContext.

Следует очень внимательно следить за порядком добавления определений бинов, так как эти условия оцениваются на основе того, что уже обработано. По этой причине рекомендуется использовать только аннотации @ConditionalOnBean и @ConditionalOnMissingBean в классах автоконфигурации (так как они гарантированно загружаются после добавления любых определений бинов, заданных пользователем).
@ConditionalOnBean и @ConditionalOnMissingBean не препятствуют созданию классов @Configuration. Единственное отличие между использованием этих условий на уровне класса и пометкой каждого вложенного метода @Bean аннотацией заключается в том, что первое предотвращает регистрацию класса @Configuration как бин, если условие не соответствует.
При объявлении метода @Bean предоставляйте как можно больше информации о типе в возвращаемом типе метода. Например, если ваш конкретный класс бина реализует интерфейс, возвращаемый тип метода бина должен быть конкретным классом, а не интерфейсом. Предоставление как можно большей информации о типе в методах @Bean особенно важно при использовании условных аннотаций для бинов, так как их оценка может полагаться только на информацию о типе, доступную в сигнатуре метода.

10.3.3. Условные аннотации для свойств

Аннотация @ConditionalOnProperty позволяет включать конфигурацию на основе свойства среды Spring. Используйте атрибуты prefix и name для указания проверяемого свойства. По умолчанию соответствует любое существующее свойство, которое не равно значению false. Вы также можете создавать более сложные проверки, используя атрибуты havingValue и matchIfMissing.

10.3.4. Условные аннотации для ресурсов

Аннотация @ConditionalOnResource позволяет включать конфигурацию только при наличии конкретного ресурса. Ресурсы можно указывать, используя стандартные соглашения Spring, как показано в следующем примере: file:/home/user/test.dat.

10.3.5. Условные аннотации для веб-приложений

Аннотации @ConditionalOnWebApplication и @ConditionalOnNotWebApplication позволяют включать конфигурацию в зависимости от того, является ли приложение веб-приложением. Веб-приложение на основе сервлетов — это любое приложение, использующее Spring WebApplicationContext, определяющее область session или имеющее ConfigurableWebEnvironment. Реактивное веб-приложение — это любое приложение, использующее ReactiveWebApplicationContext или имеющее ConfigurableReactiveWebEnvironment.

Аннотации @ConditionalOnWarDeployment и @ConditionalOnNotWarDeployment позволяют включать конфигурацию в зависимости от того, является ли приложение традиционным приложением WAR, развернутым в контейнере сервлетов. Это условие не будет соответствовать приложениям, запущенным с встроенным веб-сервером.

10.3.6. Условные аннотации для выражений SpEL

Аннотация @ConditionalOnExpression позволяет включать конфигурацию на основе результата выражения SpEL.

Ссылка на бин в выражении приведёт к инициализации этого бина на очень ранней стадии обработки контекста. В результате бин не будет подходить для пост-обработки (например, привязки свойств конфигурации) и его состояние может быть неполным.

10.4. Тестирование автоматической конфигурации

Автоматическую конфигурацию могут затрагивать многие факторы: пользовательская конфигурация (@Bean определение и Environment настройка), проверка условий (наличие определённой библиотеки) и другие. Конкретно, каждый тест должен создавать чётко определённый ApplicationContext, представляющий комбинацию этих настроек. ApplicationContextRunner предоставляет отличный способ достижения этого.

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

Java
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
    .withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration.class));
Kotlin
val contextRunner = ApplicationContextRunner()
    .withConfiguration(AutoConfigurations.of(MyServiceAutoConfiguration::class.java))
Если необходимо определить несколько автоматических конфигураций, нет необходимости упорядочивать их объявления, так как они вызываются в том же порядке, что и при запуске приложения.

Каждый тест может использовать запуск (runner) для представления конкретного сценария использования. Например, пример ниже вызывает пользовательскую конфигурацию (UserConfiguration) и проверяет, что автоматическая конфигурация корректно отказывает. Вызов run предоставляет контекст обратного вызова, который может быть использован с AssertJ.

Java
@Test
void defaultServiceBacksOff() {
    this.contextRunner.withUserConfiguration(UserConfiguration.class).run((context) -> {
        assertThat(context).hasSingleBean(MyService.class);
        assertThat(context).getBean("myCustomService").isSameAs(context.getBean(MyService.class));
    });
}

@Configuration(proxyBeanMethods = false)
static class UserConfiguration {

    @Bean
    MyService myCustomService() {
        return new MyService("mine");
    }

}
Kotlin
@Test
fun defaultServiceBacksOff() {
    contextRunner.withUserConfiguration(UserConfiguration::class.java)
        .run { context: AssertableApplicationContext ->
            assertThat(context).hasSingleBean(MyService::class.java)
            assertThat(context).getBean("myCustomService")
                .isSameAs(context.getBean(MyService::class.java))
        }
}

@Configuration(proxyBeanMethods = false)
internal class UserConfiguration {

    @Bean
    fun myCustomService(): MyService {
        return MyService("mine")
    }

}

Также можно легко настроить Environment, как показано в следующем примере:

Java
@Test
void serviceNameCanBeConfigured() {
    this.contextRunner.withPropertyValues("user.name=test123").run((context) -> {
        assertThat(context).hasSingleBean(MyService.class);
        assertThat(context.getBean(MyService.class).getName()).isEqualTo("test123");
    });
}
Kotlin
@Test
fun serviceNameCanBeConfigured() {
    contextRunner.withPropertyValues("user.name=test123").run { context: AssertableApplicationContext ->
        assertThat(context).hasSingleBean(MyService::class.java)
        assertThat(context.getBean(MyService::class.java).name).isEqualTo("test123")
    }
}

Запуск (runner) также может использоваться для отображения ConditionEvaluationReport. Отчёт может быть напечатан на уровне INFO или DEBUG. Следующий пример демонстрирует, как использовать ConditionEvaluationReportLoggingListener для печати отчёта в тестах автоматической конфигурации.

Java
import org.junit.jupiter.api.Test;

import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;

class MyConditionEvaluationReportingTests {

    @Test
    void autoConfigTest() {
        new ApplicationContextRunner()
            .withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
            .run((context) -> {
                // Test something...
            });
    }

}
Kotlin
import org.junit.jupiter.api.Test
import org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
import org.springframework.boot.logging.LogLevel
import org.springframework.boot.test.context.assertj.AssertableApplicationContext
import org.springframework.boot.test.context.runner.ApplicationContextRunner

class MyConditionEvaluationReportingTests {

    @Test
    fun autoConfigTest() {
        ApplicationContextRunner()
            .withInitializer(ConditionEvaluationReportLoggingListener.forLogLevel(LogLevel.INFO))
            .run { context: AssertableApplicationContext? -> }
    }

}

10.4.1. Моделирование веб-контекста

Если необходимо протестировать автоматическую конфигурацию, которая работает только в контексте сервлета или реактивного веб-приложения, используйте WebApplicationContextRunner или ReactiveWebApplicationContextRunner соответственно.

10.4.2. Переопределение класса

Также можно проверить, что произойдёт, если определённый класс и/или пакет отсутствуют во время выполнения. Spring Boot поставляется с FilteredClassLoader, который легко использовать с помощью запуска (runner). В следующем примере мы утверждаем, что если MyService отсутствует, автоматическая конфигурация корректно отключена:

Java
@Test
void serviceIsIgnoredIfLibraryIsNotPresent() {
    this.contextRunner.withClassLoader(new FilteredClassLoader(MyService.class))
        .run((context) -> assertThat(context).doesNotHaveBean("myService"));
}
Kotlin
@Test
fun serviceIsIgnoredIfLibraryIsNotPresent() {
    contextRunner.withClassLoader(FilteredClassLoader(MyService::class.java))
        .run { context: AssertableApplicationContext? ->
            assertThat(context).doesNotHaveBean("myService")
        }
}

10.5. Создание собственного стартера

Типичный стартер Spring Boot содержит код для автоматической настройки и настройки инфраструктуры заданной технологии, назовём её "acme". Для простоты расширения, в специальном пространстве имён могут быть доступны различные ключи конфигурации для среды. И, наконец, для облегчения начала работы пользователями предоставляется одна зависимость "starter".

Конкретно, пользовательский стартер может содержать следующее:

  • Модуль autoconfigure, содержащий код автоматической настройки для "acme".

  • Модуль starter, предоставляющий зависимость от модуля autoconfigure, а также от "acme" и любых дополнительных зависимостей, которые обычно полезны. Коротко говоря, добавление стартера должно предоставить всё необходимое для начала использования этой библиотеки.

Это разделение на два модуля необязательно. Если у "acme" есть несколько вариантов, опций или дополнительных функций, то лучше разделить автоматическую настройку, так как вы сможете чётко выразить, что некоторые функции являются необязательными. Кроме того, у вас есть возможность создать стартер, который выражает мнение об этих необязательных зависимостях. В то же время другие могут полагаться только на модуль autoconfigure и создавать свои собственные стартеры с другими мнениями.

Если автоматическая настройка относительно проста и не имеет необязательных функций, объединение двух модулей в стартере — определённо вариант.

10.5.1. Имена

Вы должны убедиться, что для вашего стартера предоставлено соответствующее пространство имён. Не начинайте имена своих модулей с spring-boot, даже если вы используете другой Maven groupId. В будущем мы можем предложить официальную поддержку того, что вы настраиваете автоматически.

Как правило, комбинированный модуль следует именовать по имени стартера. Например, предположим, что вы создаёте стартер для "acme", и вы называете модуль автоматической настройки acme-spring-boot, а стартер — acme-spring-boot-starter. Если у вас только один модуль, объединяющий оба, назовите его acme-spring-boot-starter.

10.5.2. Ключи конфигурации

Если ваш стартер предоставляет ключи конфигурации, используйте уникальное пространство имён для них. В частности, не включайте свои ключи в пространства имён, используемые Spring Boot (например, server, management, spring и так далее). Если вы используете то же пространство имён, мы можем в будущем изменить эти пространства имён таким образом, что ваши модули сломаются. Как правило, добавляйте префикс ко всем вашим ключам с собственным пространством имён (например, acme).

Убедитесь, что ключи конфигурации документированы путём добавления Javadoc для каждого свойства, как показано в следующем примере:

Java
import java.time.Duration;

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("acme")
public class AcmeProperties {

    /**
     * Whether to check the location of acme resources.
     */
    private boolean checkLocation = true;

    /**
     * Timeout for establishing a connection to the acme server.
     */
    private Duration loginTimeout = Duration.ofSeconds(3);

    // getters/setters ...

    public boolean isCheckLocation() {
        return this.checkLocation;
    }

    public void setCheckLocation(boolean checkLocation) {
        this.checkLocation = checkLocation;
    }

    public Duration getLoginTimeout() {
        return this.loginTimeout;
    }

    public void setLoginTimeout(Duration loginTimeout) {
        this.loginTimeout = loginTimeout;
    }

}
Kotlin
import org.springframework.boot.context.properties.ConfigurationProperties
import java.time.Duration

@ConfigurationProperties("acme")
class AcmeProperties(

    /**
     * Whether to check the location of acme resources.
     */
    var isCheckLocation: Boolean = true,

    /**
     * Timeout for establishing a connection to the acme server.
     */
    var loginTimeout:Duration = Duration.ofSeconds(3))
Вы должны использовать только обычный текст с Javadoc поля @ConfigurationProperties, поскольку они не обрабатываются перед добавлением в JSON.

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

  • Не начинайте описание со слов "The" или "A".

  • Для типов boolean начинайте описание со слов "Whether" или "Enable".

  • Для типов, основанных на коллекциях, начинайте описание с "Comma-separated list".

  • Используйте java.time.Duration вместо long и описывайте единицу по умолчанию, если она отличается от миллисекунд, например: "Если суффикс длительности не указан, будут использованы секунды".

  • Не указывайте значение по умолчанию в описании, если оно не должно определяться во время выполнения.

Убедитесь, что сгенерировано метаданные, чтобы в IDE была доступна помощь по вашим ключам. Вы можете просмотреть сгенерированные метаданные (META-INF/spring-configuration-metadata.json), чтобы убедиться, что ваши ключи должным образом документированы. Использование собственного стартера в совместимой IDE — хорошая идея для проверки качества метаданных.

10.5.3. Модуль «autoconfigure»

Модуль autoconfigure содержит всё необходимое для начала работы с библиотекой. Он также может содержать определения ключей конфигурации (например, @ConfigurationProperties) и любой интерфейс обратного вызова, который может использоваться для дальнейшей настройки инициализации компонентов.

Вы должны пометить зависимости от библиотеки как необязательные, чтобы вы могли легче включать модуль autoconfigure в свои проекты. В таком случае библиотека не предоставляется, и Spring Boot, по умолчанию, отказывается от неё.

Spring Boot использует процессор аннотаций для сбора условий автоматической настройки в файле метаданных (META-INF/spring-autoconfigure-metadata.properties). Если этот файл присутствует, он используется для раннего фильтрации автоматических настроек, которые не соответствуют условиям, что улучшит время запуска.

При сборке с Maven рекомендуется добавить следующую зависимость в модуль, содержащий автоматические настройки:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure-processor</artifactId>
    <optional>true</optional>
</dependency>

Если вы определили автоматические настройки непосредственно в своём приложении, убедитесь, что настроили spring-boot-maven-plugin, чтобы предотвратить добавление зависимости в fat jar инструментом repackage:

<project>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.springframework.boot</groupId>
                            <artifactId>spring-boot-autoconfigure-processor</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

С Gradle зависимость должна быть объявлена в конфигурации annotationProcessor, как показано в следующем примере:

dependencies {
    annotationProcessor "org.springframework.boot:spring-boot-autoconfigure-processor"
}

10.5.4. Модуль стартера

Стартер — это фактически пустой jar-файл. Его единственная цель — предоставить необходимые зависимости для работы с библиотекой. Его можно рассматривать как предвзятый взгляд на то, что необходимо для начала работы.

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

В любом случае ваш стартер должен ссылаться на основной стартер Spring Boot (spring-boot-starter) напрямую или косвенно (нет необходимости добавлять его, если ваш стартер опирается на другой стартер). Если проект создан только с вашим пользовательским стартером, основные функции Spring Boot будут учтены благодаря наличию основного стартера.

11. Поддержка Kotlin

Kotlin — это статически типизированный язык, ориентированный на JVM (и другие платформы), который позволяет писать лаконичный и элегантный код, обеспечивая совместимость с существующими библиотеками на Java.

Spring Boot обеспечивает поддержку Kotlin, используя поддержку других проектов Spring, таких как Spring Framework, Spring Data и Reactor. Дополнительную информацию можно найти в документации по поддержке Kotlin в Spring Framework.

Самый простой способ начать работу с Spring Boot и Kotlin — следовать этому исчерпывающему учебнику. Вы можете создавать новые проекты на Kotlin, используя start.spring.io. Не стесняйтесь присоединиться к каналу #spring на Kotlin Slack или задать вопрос с тегами spring и kotlin на Stack Overflow, если вам нужна помощь.

11.1. Требования

Spring Boot требует как минимум Kotlin 1.7.x и управляет подходящей версией Kotlin через управление зависимостями. Для использования Kotlin, org.jetbrains.kotlin:kotlin-stdlib и org.jetbrains.kotlin:kotlin-reflect должны быть доступны в классе. Также могут использоваться варианты kotlin-stdlib kotlin-stdlib-jdk7 и kotlin-stdlib-jdk8.

Поскольку классы Kotlin по умолчанию являются final, вам, вероятно, потребуется настроить плагин kotlin-spring, чтобы автоматически открывать аннотированные Spring классы, чтобы их можно было проксировать.

Модуль Jackson для Kotlin требуется для сериализации/десериализации данных JSON в Kotlin. Он автоматически регистрируется, если найден в классе. Если Jackson и Kotlin присутствуют, но модуль Jackson для Kotlin отсутствует, выводится предупреждение.

Эти зависимости и плагины предоставляются по умолчанию, если вы создаете проект Kotlin на start.spring.io.

11.2. Безопасность от null

Одной из ключевых особенностей Kotlin является безопасность от null. Она обрабатывает null значения во время компиляции, а не откладывает проблему до выполнения и не сталкивается с NullPointerException. Это помогает устранить распространенную причину ошибок без необходимости оберток, таких как Optional. Kotlin также позволяет использовать функциональные конструкции с nullable значениями, как описано в этом исчерпывающем руководстве по безопасности от null в Kotlin.

Хотя Java не позволяет выражать безопасность от null в своей системе типов, Spring Framework, Spring Data и Reactor теперь обеспечивают безопасность от null своих API с помощью удобных для инструментов аннотаций. По умолчанию типы из Java-API, используемые в Kotlin, распознаются как платформенные типы, для которых проверки null ослаблены. Поддержка Kotlin для аннотаций JSR 305 в сочетании с аннотациями для обработки null обеспечивают безопасность от null для соответствующего Spring API в Kotlin.

Проверки JSR 305 можно настроить, добавив флаг компилятора -Xjsr305 со следующими параметрами: -Xjsr305={strict|warn|ignore}. По умолчанию поведение такое же, как -Xjsr305=warn. Значение strict требуется, чтобы безопасность от null учитывалась в типах Kotlin, выведенных из Spring API, но следует использовать с пониманием того, что объявления nullability Spring API могут меняться даже между незначительными выпусками, и в будущем могут быть добавлены дополнительные проверки).

Универсальные типы аргументов, varargs и элементы массивов nullable пока не поддерживаются. Смотрите SPR-15942 за актуальной информацией. Также обратите внимание, что собственный API Spring Boot еще не аннотирован.

11.3. Kotlin API

11.3.1. runApplication

Spring Boot предоставляет удобный способ запуска приложения с runApplication<MyApplication>(*args), как показано в следующем примере:

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class MyApplication

fun main(args: Array<String>) {
    runApplication<MyApplication>(*args)
}

Это полная замена для SpringApplication.run(MyApplication::class.java, *args). Также позволяет настраивать приложение, как показано в следующем примере:

runApplication<MyApplication>(*args) {
    setBannerMode(OFF)
}

11.3.2. Расширения

Kotlin расширения предоставляют возможность расширять существующие классы дополнительными функциями. Spring Boot Kotlin API использует эти расширения для добавления новых удобств, специфичных для Kotlin, в существующие API.

TestRestTemplate расширения, аналогичные тем, которые предоставляет Spring Framework для RestOperations в Spring Framework, предоставляются. Среди прочего, расширения позволяют воспользоваться преимуществами реифицированных параметров типов Kotlin.

11.4. Управление зависимостями

Для избежания смешивания разных версий зависимостей Kotlin в классе, Spring Boot импортирует BOM Kotlin.

В Maven версия Kotlin может быть настроена путем установки свойства kotlin.version, и управление плагинами предоставляется для kotlin-maven-plugin. В Gradle плагин Spring Boot автоматически согласует kotlin.version с версией плагина Kotlin.

Spring Boot также управляет версией зависимостей Coroutines, импортируя BOM Kotlin Coroutines. Версию можно настроить, установив свойство kotlin-coroutines.version.

org.jetbrains.kotlinx:kotlinx-coroutines-reactor зависимость предоставляется по умолчанию, если вы создаете проект Kotlin с хотя бы одной реактивной зависимостью на start.spring.io.

11.5. @ConfigurationProperties

@ConfigurationProperties при использовании в сочетании с связыванием по конструктору поддерживает классы с неизменяемыми val свойствами, как показано в следующем примере:

@ConfigurationProperties("example.kotlin")
data class KotlinExampleProperties(
        val name: String,
        val description: String,
        val myService: MyService) {

    data class MyService(
            val apiToken: String,
            val uri: URI
    )
}
Чтобы сгенерировать свою метаданные с помощью обработчика аннотаций, kapt следует настроить с зависимостью spring-boot-configuration-processor. Обратите внимание, что некоторые функции (например, обнаружение значения по умолчанию или устаревших элементов) не работают из-за ограничений в модели, предоставляемой kapt.

11.6. Тестирование

Хотя можно использовать JUnit 4 для тестирования кода Kotlin, по умолчанию предоставляется и рекомендуется JUnit 5. JUnit 5 позволяет создавать экземпляр тестового класса один раз и использовать его повторно для всех тестов класса. Это позволяет использовать @BeforeAll и @AfterAll аннотации на нестатических методах, что хорошо подходит для Kotlin.

Для создания mocks классов Kotlin рекомендуется использовать MockK. Если вам нужен эквивалент MockK аннотаций Mockito, таких как @MockBean и @SpyBean аннотации, вы можете использовать SpringMockK, который предоставляет аналогичные аннотации @MockkBean и @SpykBean.

11.7. Ресурсы

11.7.1. Дополнительное чтение

  • Справочник по языку Kotlin

  • Kotlin Slack (с выделенным каналом #spring)

  • Stack Overflow с тегами spring и kotlin

  • Попробуйте Kotlin в вашем браузере

  • Блог Kotlin

  • Awesome Kotlin

  • Учебник: создание веб-приложений с Spring Boot и Kotlin

  • Разработка приложений Spring Boot с Kotlin

  • Геопространственный мессенджер с Kotlin, Spring Boot и PostgreSQL

  • Введение в поддержку Kotlin в Spring Framework 5.0

  • Spring Framework 5 Kotlin API, функциональный подход

11.7.2. Примеры

  • spring-boot-kotlin-demo: обычный проект Spring Boot + Spring Data JPA

  • mixit: Spring Boot 2 + WebFlux + Reactive Spring Data MongoDB

  • spring-kotlin-fullstack: пример полнофункционального приложения WebFlux на Kotlin с Kotlin2js для фронтенда вместо JavaScript или TypeScript

  • spring-petclinic-kotlin: версия приложения Spring PetClinic на Kotlin

  • spring-kotlin-deepdive: пошаговая миграция от Boot 1.0 + Java к Boot 2.0 + Kotlin

  • spring-boot-coroutines-demo: пример проекта с использованием Coroutines

12. SSL

Spring Boot предоставляет возможность настроить SSL-материалы доверия, которые могут применяться к нескольким типам соединений для поддержки защищенной связи. Свойства конфигурации с префиксом spring.ssl.bundle могут использоваться для указания именованных наборов материалов доверия и связанной информации.

12.1. Настройка SSL с файлами хранилища ключей Java

Свойства конфигурации с префиксом spring.ssl.bundle.jks могут использоваться для настройки наборов материалов доверия, созданных с помощью утилиты Java keytool и сохранённых в файлах хранилища ключей Java в формате JKS или PKCS12. Каждый набор имеет предоставленное пользователем имя, которое может быть использовано для ссылки на набор.

При использовании для защиты встроенного веб-сервера, обычно настраивается keystore с хранилищем ключей Java, содержащим сертификат и закрытый ключ, как показано в этом примере:

Свойства
spring.ssl.bundle.jks.mybundle.key.alias=application
spring.ssl.bundle.jks.mybundle.keystore.location=classpath:application.p12
spring.ssl.bundle.jks.mybundle.keystore.password=secret
spring.ssl.bundle.jks.mybundle.keystore.type=PKCS12
Yaml
spring:
  ssl:
    bundle:
      jks:
        mybundle:
          key:
            alias: "application"
          keystore:
            location: "classpath:application.p12"
            password: "secret"
            type: "PKCS12"

При использовании для защиты клиентского подключения, обычно настраивается truststore с хранилищем ключей Java, содержащим сертификат сервера, как показано в этом примере:

Свойства
spring.ssl.bundle.jks.mybundle.truststore.location=classpath:server.p12
spring.ssl.bundle.jks.mybundle.truststore.password=secret
Yaml
spring:
  ssl:
    bundle:
      jks:
        mybundle:
          truststore:
            location: "classpath:server.p12"
            password: "secret"

См. JksSslBundleProperties для полного набора поддерживаемых свойств.

12.2. Настройка SSL с PEM-кодированными сертификатами

Свойства конфигурации с префиксом spring.ssl.bundle.pem могут использоваться для настройки наборов материалов доверия в виде PEM-кодированного текста. Каждый набор имеет предоставленное пользователем имя, которое может быть использовано для ссылки на набор.

При использовании для защиты встроенного веб-сервера, обычно настраивается keystore с сертификатом и закрытым ключом, как показано в этом примере:

Свойства
spring.ssl.bundle.pem.mybundle.keystore.certificate=classpath:application.crt
spring.ssl.bundle.pem.mybundle.keystore.private-key=classpath:application.key
Yaml
spring:
  ssl:
    bundle:
      pem:
        mybundle:
          keystore:
            certificate: "classpath:application.crt"
            private-key: "classpath:application.key"

При использовании для защиты клиентского подключения, обычно настраивается truststore с сертификатом сервера, как показано в этом примере:

Свойства
spring.ssl.bundle.pem.mybundle.truststore.certificate=classpath:server.crt
Yaml
spring:
  ssl:
    bundle:
      pem:
        mybundle:
          truststore:
            certificate: "classpath:server.crt"

См. PemSslBundleProperties для полного набора поддерживаемых свойств.

12.3. Применение наборов SSL

После настройки с помощью свойств, наборы SSL могут быть сосланы по имени в свойствах конфигурации для различных типов подключений, которые автоматически настраиваются Spring Boot. Смотрите разделы по встроенным веб-серверам, технологиям данных и клиентам REST для получения дополнительной информации.

12.4. Использование наборов SSL

Spring Boot автоматически настраивает бин типа SslBundles, который предоставляет доступ к каждому из настроенных именованных наборов с помощью свойств spring.ssl.bundle.

SslBundle может быть получен из автоматически настроенного бин SslBundles и использован для создания объектов, которые используются для настройки SSL-соединений в клиентских библиотеках. SslBundle предоставляет послойный подход к получению этих SSL-объектов:

  • getStores() предоставляет доступ к хранилищу ключей и хранилищу сертификатов java.security.KeyStore экземплярам, а также к любому необходимому паролю хранилища ключей.

  • getManagers() предоставляет доступ к java.net.ssl.KeyManagerFactory и java.net.ssl.TrustManagerFactory экземплярам, а также к массивам java.net.ssl.KeyManager и java.net.ssl.TrustManager, которые они создают.

  • createSslContext() предоставляет удобный способ получения нового java.net.ssl.SSLContext экземпляра.

Кроме того, SslBundle предоставляет сведения о используемом ключе, протоколе и любых параметрах, которые должны быть применены к SSL-движку.

Следующий пример показывает получение SslBundle и использование его для создания SSLContext:

Java
import javax.net.ssl.SSLContext;

import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslBundles;
import org.springframework.stereotype.Component;

@Component
public class MyComponent {

    public MyComponent(SslBundles sslBundles) {
        SslBundle sslBundle = sslBundles.getBundle("mybundle");
        SSLContext sslContext = sslBundle.createSslContext();
        // do something with the created sslContext
    }

}
Kotlin
import org.springframework.boot.ssl.SslBundles
import org.springframework.stereotype.Component

@Component
class MyComponent(sslBundles: SslBundles) {

    init {
        val sslBundle = sslBundles.getBundle("mybundle")
        val sslContext = sslBundle.createSslContext()
        // do something with the created sslContext
    }

}

13. Что читать дальше

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

Если вы знакомы с основными функциями Spring Boot, вы можете продолжить чтение о функциях, готовых к использованию в производстве.

Copyright © 2012-2023 VMware, Inc.
Licensed under the Apache License, Version 2.0.
https://docs.spring.io/spring-boot/docs/3.1.3/reference/html/features.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API