Spring Boot 3.4: Усі нововведення детально

Spring Boot 3.4 приносить нативне структуроване логування, розширені virtual threads, graceful shutdown за замовчуванням та MockMvcTester. Повний огляд нових можливостей.

Нові можливості та покращення Spring Boot 3.4

Spring Boot 3.4, випущений у листопаді 2024 року, надає значні покращення в продуктивності розробників та швидкодії додатків. Ця версія впроваджує нативне структуроване логування, активує graceful shutdown за замовчуванням та розширює підтримку virtual threads у всьому фреймворку.

Передумови

Spring Boot 3.4 вимагає мінімум Java 17 та підтримує Java 21 для virtual threads. Ця версія використовує Spring Framework 6.2.

Нативне структуроване логування

Структуроване логування являє собою важливий крок вперед для спостережуваності додатків. Замість текстових логів, які важко аналізувати, Spring Boot 3.4 генерує логи у форматі JSON, придатні для обробки інструментами на кшталт Elasticsearch, Grafana Loki чи Datadog.

Три формати підтримуються нативно: Elastic Common Schema (ECS), Logstash та Graylog Extended Log Format (GELF).

properties
# application.properties
# Enable structured logging in console
logging.structured.format.console=ecs

# Or for log files
logging.structured.format.file=logstash

Ця проста конфігурація автоматично генерує форматовані структуровані JSON-логи.

LoggingController.javajava
@RestController
@RequestMapping("/api/demo")
public class LoggingController {

    // Logger injection via SLF4J
    private static final Logger logger = LoggerFactory.getLogger(LoggingController.class);

    @GetMapping("/action")
    public ResponseEntity<String> performAction(@RequestParam String userId) {
        // Log will be automatically formatted as structured JSON
        logger.info("Action performed by user: {}", userId);
        return ResponseEntity.ok("Action completed");
    }
}

При увімкненому форматі ECS цей лог перетворюється на JSON-об'єкт, що містить timestamp, рівень, повідомлення, назву класу, потік та метадані додатку. Така структура спрощує пошук та агрегацію на платформах моніторингу.

Graceful Shutdown увімкнений за замовчуванням

Важлива зміна: graceful shutdown тепер увімкнений за замовчуванням. Поточні HTTP-запити обробляються перед зупинкою сервера, що запобігає помилкам 502 під час розгортання.

properties
# application.properties
# Graceful shutdown is now ON by default
# To restore previous behavior (immediate shutdown):
server.shutdown=immediate

# Configure maximum wait timeout (30s default)
spring.lifecycle.timeout-per-shutdown-phase=45s

Ця поведінка застосовується до всіх вбудованих серверів: Tomcat, Jetty, Undertow та Reactor Netty.

LifecycleConfig.javajava
@Configuration
public class LifecycleConfig {

    private static final Logger logger = LoggerFactory.getLogger(LifecycleConfig.class);

    @Bean
    public ApplicationListener<ContextClosedEvent> gracefulShutdownListener() {
        // This listener executes at shutdown start
        return event -> {
            logger.info("Graceful shutdown initiated - completing in-flight requests");
            // Custom logic: close connections, save state, etc.
        };
    }
}

Це покращення спрощує реалізацію розгортань без простою, без потреби додаткової конфігурації у більшості випадків.

Розширена підтримка Virtual Threads

Spring Boot 3.4 розширює підтримку virtual threads (Java 21+) на більшу кількість компонентів. OtlpMeterRegistry та сервер Undertow тепер використовують virtual threads при їх активації.

properties
# application.properties
# Enable virtual threads globally
spring.threads.virtual.enabled=true

Ця єдина властивість трансформує модель потоків додатку. Кожен HTTP-запит отримує власний virtual thread, що дозволяє тисячі одночасних з'єднань без вичерпання пулу потоків.

AsyncService.javajava
@Service
public class AsyncService {

    private static final Logger logger = LoggerFactory.getLogger(AsyncService.class);

    // With virtual threads enabled, each blocking call
    // no longer consumes an OS thread
    public String fetchExternalData() {
        logger.info("Executing on thread: {}", Thread.currentThread());

        // Blocking HTTP call - with virtual threads,
        // the OS thread is released during I/O wait
        return restClient.get()
            .uri("https://api.external.com/data")
            .retrieve()
            .body(String.class);
    }
}

Virtual threads демонструють найкращі результати у додатках з інтенсивним I/O: HTTP-виклики, запити до баз даних, файлові операції. Потік операційної системи звільняється під час очікування та повторно призначається при відновленні.

Сумісність

Virtual threads вимагають мінімум Java 21. На Java 17 ця властивість ігнорується і застосовується класична поведінка.

Покращений RestClient та RestTemplate

Spring Boot 3.4 нормалізує конфігурацію HTTP-клієнта. Вибір HttpRequestFactory тепер дотримується чіткої послідовності на основі classpath.

HttpClientConfig.javajava
@Configuration
public class HttpClientConfig {

    @Bean
    public RestClient restClient(RestClient.Builder builder) {
        // Spring Boot automatically chooses implementation:
        // 1. Apache HTTP Components (if present)
        // 2. Jetty Client
        // 3. Reactor Netty
        // 4. JDK HttpClient (Java 11+)
        // 5. SimpleClientHttpRequestFactory (fallback)
        return builder
            .baseUrl("https://api.example.com")
            .defaultHeader("Accept", "application/json")
            .build();
    }
}

Конфігурація поведінки перенаправлень також спрощена.

properties
# application.properties
# Force specific implementation
spring.http.client.factory=jdk

# Configure redirect behavior
spring.http.client.redirects=dont-follow

Для більш детального контролю новий ClientHttpRequestFactoryBuilder дозволяє повну програмну конфігурацію.

CustomHttpClientConfig.javajava
@Configuration
public class CustomHttpClientConfig {

    @Bean
    public RestClient customRestClient(ClientHttpRequestFactoryBuilder factoryBuilder) {
        // Advanced factory configuration
        ClientHttpRequestFactory factory = factoryBuilder
            .httpComponents()
            .connectTimeout(Duration.ofSeconds(5))
            .readTimeout(Duration.ofSeconds(30))
            .build();

        return RestClient.builder()
            .requestFactory(factory)
            .baseUrl("https://api.example.com")
            .build();
    }
}

Цей підхід забезпечує максимальну гнучкість зі збереженням консистентного API незалежно від базової HTTP-реалізації.

Готовий до співбесід з Spring Boot?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

MockMvcTester для зручного тестування

Spring Boot 3.4 представляє MockMvcTester, альтернативу MockMvc на основі AssertJ. Цей новий підхід робить тести більш читабельними та виразними.

UserControllerTest.javajava
@WebMvcTest(UserController.class)
class UserControllerTest {

    @Autowired
    private MockMvcTester mockMvc; // New class!

    @Test
    void shouldReturnUserById() {
        // Fluent API with AssertJ
        mockMvc.get().uri("/api/users/{id}", 1)
            .assertThat()
            .hasStatusOk()
            .hasContentType(MediaType.APPLICATION_JSON)
            .bodyJson()
            .extractingPath("$.name")
            .isEqualTo("John Doe");
    }

    @Test
    void shouldReturn404ForUnknownUser() {
        mockMvc.get().uri("/api/users/{id}", 999)
            .assertThat()
            .hasStatus(HttpStatus.NOT_FOUND)
            .bodyJson()
            .extractingPath("$.error")
            .isEqualTo("User not found");
    }
}

Порівняно з попереднім підходом із MockMvc, перевірки стали більш лаконічними, а ланцюжки виглядають природніше.

ComparisonTest.javajava
@WebMvcTest(UserController.class)
class ComparisonTest {

    @Autowired
    private MockMvcTester mockMvcTester;

    @Autowired
    private MockMvc mockMvc;

    @Test
    void withMockMvcTester() {
        // New approach: fluent and concise
        mockMvcTester.post().uri("/api/users")
            .contentType(MediaType.APPLICATION_JSON)
            .content("{\"name\": \"Jane\"}")
            .assertThat()
            .hasStatus(HttpStatus.CREATED)
            .hasHeader("Location", "/api/users/2");
    }

    @Test
    void withClassicMockMvc() throws Exception {
        // Old approach: more verbose
        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content("{\"name\": \"Jane\"}"))
            .andExpect(status().isCreated())
            .andExpect(header().string("Location", "/api/users/2"));
    }
}

MockMvcTester автоматично налаштовується, коли AssertJ присутній у classpath (за замовчуванням включений у spring-boot-starter-test).

Покращений Docker Compose та Testcontainers

Підтримка Docker Compose набуває гнучкості завдяки кільком файлам конфігурації та користувацьким аргументам.

properties
# application.properties
# Use multiple Docker Compose files
spring.docker.compose.file=compose.yaml,compose-dev.yaml

# Pass arguments at startup
spring.docker.compose.start.arguments=--scale redis=2

# Arguments at shutdown
spring.docker.compose.stop.arguments=--timeout 60

Нові сервіси автоматично виявляються та налаштовуються.

yaml
# compose.yaml
services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    ports:
      - "5432:5432"

  redis-stack:
    image: redis/redis-stack:latest
    ports:
      - "6379:6379"
      - "8001:8001" # RedisInsight UI

  grafana-lgtm:
    image: grafana/otel-lgtm:latest
    ports:
      - "3000:3000"  # Grafana
      - "4317:4317"  # OTLP gRPC

Spring Boot 3.4 автоматично виявляє ці сервіси та налаштовує відповідні властивості з'єднання.

Для тестування з Testcontainers підтримуються нові контейнери.

IntegrationTestConfig.javajava
@TestConfiguration(proxyBeanMethods = false)
public class IntegrationTestConfig {

    @Bean
    @ServiceConnection
    public PostgreSQLContainer<?> postgresContainer() {
        return new PostgreSQLContainer<>("postgres:16");
    }

    @Bean
    @ServiceConnection
    public RedisStackContainer redisStackContainer() {
        // New Redis Stack support
        return new RedisStackContainer("redis/redis-stack:latest");
    }

    @Bean
    @ServiceConnection
    public LgtmStackContainer observabilityContainer() {
        // New Grafana LGTM support (Loki, Grafana, Tempo, Mimir)
        return new LgtmStackContainer("grafana/otel-lgtm:latest");
    }
}

Анотація @ServiceConnection автоматично налаштовує властивості з'єднання, усуваючи потребу в @DynamicPropertySource у більшості випадків.

Actuator SSL та спостережуваність

Новий endpoint /actuator/info тепер надає інформацію про налаштовані SSL-сертифікати: дати дійсності, видавець та суб'єкт.

properties
# application.properties
# Enable SSL information in actuator
management.info.ssl.enabled=true

# Configure warning threshold for expiring certificates
management.health.ssl.certificate-validity-warning-threshold=30d

Ця функціональність дозволяє моніторити закінчення терміну дії сертифікатів безпосередньо через actuator, полегшуючи автоматизацію оновлення.

SslMonitoringConfig.javajava
@Configuration
public class SslMonitoringConfig {

    @Bean
    public HealthIndicator sslCertificateHealth(SslInfo sslInfo) {
        return () -> {
            // Custom certificate validity check
            boolean allValid = sslInfo.getBundles().values().stream()
                .flatMap(bundle -> bundle.getCertificates().stream())
                .allMatch(cert -> cert.getValidityEnds().isAfter(Instant.now()));

            return allValid
                ? Health.up().build()
                : Health.down().withDetail("reason", "Certificate expiring soon").build();
        };
    }
}

Для спостережуваності OTLP-транспорт тепер підтримує gRPC на додаток до HTTP.

properties
# application.properties
# Use gRPC for OTLP (traces and metrics)
management.otlp.tracing.transport=grpc
management.otlp.tracing.endpoint=http://localhost:4317

# New: group applications together
spring.application.group=payment-services

Група додатків (spring.application.group) дозволяє логічно групувати кілька сервісів у дашбордах спостережуваності.

Легші OCI-образи

Стандартний OCI-білдер образів змінюється з paketobuildpacks/builder-jammy-base на paketobuildpacks/builder-jammy-java-tiny, створюючи значно менші образи.

build.gradlegroovy
tasks.named("bootBuildImage") {
    // Native ARM support (new)
    imagePlatform = "linux/arm64"

    // New security flag
    trustBuilder = false

    // Image configuration
    imageName = "myregistry.com/myapp:${version}"
}

Новий параметр imagePlatform спрощує кросплатформну збірку для ARM та x64.

xml
<!-- pom.xml -->
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
        <image>
            <!-- Native ARM support -->
            <platform>linux/arm64</platform>
            <!-- Builder optimized for minimal images -->
            <builder>paketobuildpacks/builder-jammy-java-tiny</builder>
        </image>
    </configuration>
</plugin>

Ці оптимізовані образи запускаються швидше та споживають менше ресурсів, що особливо корисно для розгортань у Kubernetes.

Примітка щодо міграції

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

Зміни в Bean Validation

Spring Boot 3.4 узгоджує поведінку валідації зі специфікацією Bean Validation. Валідація більше не каскадується автоматично до вкладених властивостей.

ConfigProperties.javajava
@ConfigurationProperties(prefix = "app")
@Validated
public class AppConfig {

    @NotBlank
    private String name;

    // IMPORTANT: @Valid required to cascade validation
    @Valid
    private DatabaseConfig database;

    // Without @Valid, ServerConfig constraints will NOT be checked
    private ServerConfig server;

    // Getters and setters
}

public class DatabaseConfig {
    @NotBlank
    private String url;

    @Min(1)
    private int poolSize;

    // Getters and setters
}

public class ServerConfig {
    @NotNull // This constraint will NOT be checked without @Valid on parent
    private Integer port;

    // Getters and setters
}

Ця зміна може вплинути на існуючі додатки. Слід перевірити класи @ConfigurationProperties та додати @Valid там, де валідація має каскадуватися.

Застарівання @MockBean та @SpyBean

Анотації @MockBean та @SpyBean у Spring Boot позначені як застарілі на користь нових анотацій Mockito.

UserServiceTest.java (new approach)java
@SpringBootTest
class UserServiceTest {

    // New: native Mockito annotations
    @MockitoBean
    private UserRepository userRepository;

    @MockitoSpyBean
    private EmailService emailService;

    @Autowired
    private UserService userService;

    @Test
    void shouldCreateUser() {
        // Mock configuration
        when(userRepository.save(any())).thenReturn(new User(1L, "test@example.com"));

        userService.createUser("test@example.com");

        // Spy verification
        verify(emailService).sendWelcomeEmail("test@example.com");
    }
}

Старі анотації все ще працюють, але показують попередження про застарівання. Варто запланувати міграцію на @MockitoBean та @MockitoSpyBean.

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Висновок

Spring Boot 3.4 надає суттєві покращення в продуктивності та спостережуваності:

Контрольний список міграції:

  • Увімкнути структуроване логування для покращення спостережуваності
  • Перевірити поведінку graceful shutdown (тепер увімкнений за замовчуванням)
  • Розглянути virtual threads для додатків з інтенсивним I/O (Java 21+)
  • Мігрувати на MockMvcTester для більш читабельних тестів
  • Додати @Valid на вкладені властивості @ConfigurationProperties
  • Замінити @MockBean/@SpyBean на @MockitoBean/@MockitoSpyBean
  • Протестувати нові OCI-образи перед розгортанням у продакшен

Spring Boot 3.4 зміцнює свою позицію як еталонний фреймворк для сучасної Java-розробки, з особливою увагою до стандартів спостережуваності та продуктивності.

Джерела:

Теги

#spring boot
#java
#spring boot 3.4
#spring framework
#backend

Поділитися

Пов'язані статті