Spring Boot 3.4: Wszystkie nowości szczegółowo

Spring Boot 3.4 wprowadza natywne logowanie strukturalne, rozszerzone virtual threads, domyślny graceful shutdown i MockMvcTester. Kompletny przewodnik po nowych funkcjach.

Nowe funkcje i ulepszenia w Spring Boot 3.4

Spring Boot 3.4, wydany w listopadzie 2024, dostarcza znaczące ulepszenia w zakresie produktywności programistów i wydajności aplikacji. Ta wersja wprowadza natywne logowanie strukturalne, włącza graceful shutdown domyślnie i rozszerza obsługę virtual threads w całym frameworku.

Wymagania wstępne

Spring Boot 3.4 wymaga minimum Java 17 i obsługuje Java 21 dla virtual threads. Ta wersja korzysta ze Spring Framework 6.2.

Natywne logowanie strukturalne

Logowanie strukturalne stanowi istotny postęp w obserwowalności aplikacji. Zamiast tekstowych logów trudnych do analizy, Spring Boot 3.4 generuje logi w formacie JSON, które mogą być przetwarzane przez narzędzia takie jak Elasticsearch, Grafana Loki czy Datadog.

Trzy formaty są wspierane natywnie: Elastic Common Schema (ECS), Logstash i 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

Ta prosta konfiguracja automatycznie produkuje sformatowane logi 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");
    }
}

Przy włączonym formacie ECS ten log staje się obiektem JSON zawierającym timestamp, poziom, wiadomość, nazwę klasy, wątek i metadane aplikacji. Taka struktura upraszcza wyszukiwanie i agregację na platformach monitoringu.

Graceful Shutdown włączony domyślnie

Istotna zmiana: graceful shutdown jest teraz włączony domyślnie. Trwające żądania HTTP są przetwarzane przed wyłączeniem serwera, co zapobiega błędom 502 podczas wdrożeń.

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

To zachowanie dotyczy wszystkich wbudowanych serwerów: Tomcat, Jetty, Undertow i 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.
        };
    }
}

To ulepszenie upraszcza implementację wdrożeń bez przestojów, bez potrzeby dodatkowej konfiguracji w większości przypadków.

Rozszerzona obsługa Virtual Threads

Spring Boot 3.4 rozszerza obsługę virtual threads (Java 21+) na więcej komponentów. OtlpMeterRegistry i serwer Undertow teraz korzystają z virtual threads, gdy są włączone.

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

Ta pojedyncza właściwość transformuje model wątkowania aplikacji. Każde żądanie HTTP otrzymuje własny virtual thread, co umożliwia tysiące jednoczesnych połączeń bez wyczerpywania puli wątków.

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 sprawdzają się doskonale w aplikacjach z dużym obciążeniem I/O: wywołania HTTP, zapytania do baz danych, operacje na plikach. Wątek systemu operacyjnego jest zwalniany podczas oczekiwania i ponownie przydzielany po wznowieniu.

Kompatybilność

Virtual threads wymagają minimum Java 21. Na Java 17 ta właściwość jest ignorowana i obowiązuje klasyczne zachowanie.

Ulepszony RestClient i RestTemplate

Spring Boot 3.4 normalizuje konfigurację klienta HTTP. Wybór HttpRequestFactory podąża teraz za jasną hierarchią opartą na 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();
    }
}

Konfiguracja zachowania przekierowań jest również uproszczona.

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

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

Dla bardziej szczegółowej kontroli nowy ClientHttpRequestFactoryBuilder umożliwia pełną konfigurację programatyczną.

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();
    }
}

To podejście oferuje maksymalną elastyczność przy zachowaniu spójnego API niezależnie od bazowej implementacji HTTP.

Gotowy na rozmowy o Spring Boot?

Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.

MockMvcTester do płynnego testowania

Spring Boot 3.4 wprowadza MockMvcTester, opartą na AssertJ alternatywę dla MockMvc. To nowe podejście czyni testy bardziej czytelnymi i ekspresywnymi.

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");
    }
}

W porównaniu z poprzednim podejściem z MockMvc, asercje są bardziej zwięzłe, a łańcuchowanie bardziej naturalne.

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 jest automatycznie konfigurowany, gdy AssertJ jest obecny w classpath (dołączony domyślnie z spring-boot-starter-test).

Ulepszony Docker Compose i Testcontainers

Obsługa Docker Compose zyskuje elastyczność dzięki wielu plikom konfiguracyjnym i niestandardowym argumentom.

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

Nowe usługi są automatycznie wykrywane i konfigurowane.

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 automatycznie wykrywa te usługi i konfiguruje odpowiednie właściwości połączenia.

Dla testów z Testcontainers obsługiwane są nowe kontenery.

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");
    }
}

Adnotacja @ServiceConnection automatycznie konfiguruje właściwości połączenia, eliminując potrzebę @DynamicPropertySource w większości przypadków.

Actuator SSL i obserwowalność

Nowy endpoint /actuator/info udostępnia teraz informacje o skonfigurowanych certyfikatach SSL: daty ważności, wystawca i podmiot.

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

Ta funkcjonalność umożliwia monitorowanie wygasania certyfikatów bezpośrednio przez actuator, ułatwiając automatyzację odnowień.

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();
        };
    }
}

Dla obserwowalności transport OTLP obsługuje teraz gRPC oprócz 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

Grupa aplikacji (spring.application.group) umożliwia logiczne grupowanie wielu usług w dashboardach obserwowalności.

Lżejsze obrazy OCI

Domyślny builder obrazów OCI zmienia się z paketobuildpacks/builder-jammy-base na paketobuildpacks/builder-jammy-java-tiny, produkując znacznie mniejsze obrazy.

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

    // New security flag
    trustBuilder = false

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

Nowy parametr imagePlatform upraszcza budowanie wieloplatformowe dla ARM i 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>

Te zoptymalizowane obrazy uruchamiają się szybciej i zużywają mniej zasobów, co jest szczególnie korzystne dla wdrożeń na Kubernetes.

Uwaga migracyjna

Zmiana buildera może wpłynąć na aplikacje zależne od określonych narzędzi systemowych. Nowe obrazy należy przetestować przed wdrożeniem na produkcję.

Zmiany w Bean Validation

Spring Boot 3.4 dostosowuje zachowanie walidacji do specyfikacji Bean Validation. Walidacja nie propaguje się już automatycznie do zagnieżdżonych właściwości.

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
}

Ta zmiana może wpłynąć na istniejące aplikacje. Należy skontrolować klasy @ConfigurationProperties i dodać @Valid tam, gdzie walidacja powinna się propagować.

Deprecjacja @MockBean i @SpyBean

Adnotacje @MockBean i @SpyBean ze Spring Boot są zdeprecjonowane na rzecz nowych adnotacji 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");
    }
}

Stare adnotacje nadal działają, ale wyświetlają ostrzeżenia o deprecjacji. Warto zaplanować migrację do @MockitoBean i @MockitoSpyBean.

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Podsumowanie

Spring Boot 3.4 dostarcza istotne ulepszenia w produktywności i obserwowalności:

Lista kontrolna migracji:

  • Włączyć logowanie strukturalne dla lepszej obserwowalności
  • Zweryfikować zachowanie graceful shutdown (teraz włączone domyślnie)
  • Rozważyć virtual threads dla aplikacji z dużym obciążeniem I/O (Java 21+)
  • Migrować do MockMvcTester dla bardziej czytelnych testów
  • Dodać @Valid przy zagnieżdżonych właściwościach @ConfigurationProperties
  • Zastąpić @MockBean/@SpyBean przez @MockitoBean/@MockitoSpyBean
  • Przetestować nowe obrazy OCI przed wdrożeniem na produkcję

Spring Boot 3.4 umacnia swoją pozycję jako referencyjny framework dla nowoczesnego rozwoju w Java, ze szczególnym naciskiem na standardy obserwowalności i wydajność.

Źródła:

Tagi

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

Udostępnij

Powiązane artykuły