Spring Boot 3.4: ฟีเจอร์ใหม่ทั้งหมดอธิบายครบถ้วน

Spring Boot 3.4 นำเสนอ structured logging แบบ native, virtual threads ที่ขยายเพิ่ม, graceful shutdown เป็นค่าเริ่มต้น และ MockMvcTester คู่มือฉบับสมบูรณ์ของฟีเจอร์ใหม่

ฟีเจอร์ใหม่และการปรับปรุงใน Spring Boot 3.4

Spring Boot 3.4 เปิดตัวในเดือนพฤศจิกายน 2024 พร้อมการปรับปรุงที่สำคัญด้านประสิทธิภาพการพัฒนาและประสิทธิภาพของแอปพลิเคชัน เวอร์ชันนี้แนะนำ structured logging แบบ native เปิดใช้งาน graceful shutdown เป็นค่าเริ่มต้น และขยายการรองรับ virtual threads ทั่วทั้ง framework

ข้อกำหนดเบื้องต้น

Spring Boot 3.4 ต้องการ Java 17 เป็นขั้นต่ำ และรองรับ Java 21 สำหรับ virtual threads เวอร์ชันนี้ใช้ Spring Framework 6.2

Structured logging แบบ native

Structured logging ถือเป็นก้าวสำคัญสำหรับ observability ของแอปพลิเคชัน แทนที่จะใช้ log แบบข้อความที่วิเคราะห์ได้ยาก Spring Boot 3.4 สร้าง log ในรูปแบบ JSON ที่สามารถประมวลผลโดยเครื่องมืออย่าง Elasticsearch, Grafana Loki หรือ Datadog

สามรูปแบบที่รองรับแบบ native ได้แก่ 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

การตั้งค่าง่ายๆ นี้จะสร้าง structured JSON log โดยอัตโนมัติ

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 log นี้จะกลายเป็นอ็อบเจกต์ JSON ที่มี timestamp, level, ข้อความ, ชื่อคลาส, thread และ metadata ของแอปพลิเคชัน โครงสร้างนี้ช่วยให้การค้นหาและการรวบรวมข้อมูลบนแพลตฟอร์มการตรวจสอบทำได้ง่ายขึ้น

Graceful Shutdown เปิดใช้งานเป็นค่าเริ่มต้น

การเปลี่ยนแปลงสำคัญ: graceful shutdown เปิดใช้งานเป็นค่าเริ่มต้นแล้ว HTTP request ที่กำลังดำเนินการจะถูกประมวลผลจนเสร็จก่อนที่เซิร์ฟเวอร์จะปิด ป้องกันข้อผิดพลาด 502 ระหว่างการ deploy

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

พฤติกรรมนี้ใช้ได้กับ embedded server ทุกตัว: 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.
        };
    }
}

การปรับปรุงนี้ทำให้การ deploy แบบไม่มี downtime ง่ายขึ้น โดยไม่ต้องตั้งค่าเพิ่มเติมในกรณีส่วนใหญ่

การรองรับ 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

property เดียวนี้เปลี่ยนโมเดล threading ของแอปพลิเคชัน HTTP request แต่ละตัวจะได้รับ virtual thread ของตัวเอง ทำให้รองรับการเชื่อมต่อพร้อมกันนับพันได้โดยไม่ทำให้ thread pool หมด

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, การ query ฐานข้อมูล, การดำเนินการกับไฟล์ OS thread จะถูกปล่อยระหว่างเวลารอและถูกกำหนดใหม่เมื่อทำงานต่อ

ความเข้ากันได้

Virtual threads ต้องการ Java 21 เป็นขั้นต่ำ บน Java 17 property นี้จะถูกละเว้นและใช้พฤติกรรมแบบดั้งเดิม

RestClient และ RestTemplate ที่ปรับปรุงแล้ว

Spring Boot 3.4 ทำให้การตั้งค่า HTTP client เป็นมาตรฐาน การเลือก 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();
    }
}

การตั้งค่าพฤติกรรม redirect ก็ถูกทำให้ง่ายขึ้นเช่นกัน

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

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

สำหรับการควบคุมที่ละเอียดยิ่งขึ้น ClientHttpRequestFactoryBuilder ตัวใหม่ช่วยให้ตั้งค่าแบบ programmatic ได้อย่างสมบูรณ์

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 implementation ตัวใด

พร้อมที่จะพิชิตการสัมภาษณ์ Spring Boot แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

MockMvcTester สำหรับการทดสอบที่ลื่นไหล

Spring Boot 3.4 แนะนำ MockMvcTester ทางเลือกที่ใช้ AssertJ แทน MockMvc แนวทางใหม่นี้ทำให้ test อ่านง่ายขึ้นและแสดงออกได้มากขึ้น

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 การ assert กระชับขึ้นและการ chain เป็นธรรมชาติมากขึ้น

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 มีความยืดหยุ่นมากขึ้นด้วยไฟล์การตั้งค่าหลายไฟล์และ argument ที่กำหนดเอง

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

service ใหม่จะถูกตรวจจับและตั้งค่าโดยอัตโนมัติ

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 ตรวจจับ service เหล่านี้โดยอัตโนมัติและตั้งค่า property การเชื่อมต่อที่เกี่ยวข้อง

สำหรับการทดสอบด้วย Testcontainers มี container ใหม่ที่รองรับ

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

annotation @ServiceConnection ตั้งค่า property การเชื่อมต่อโดยอัตโนมัติ ลดความจำเป็นในการใช้ @DynamicPropertySource ในกรณีส่วนใหญ่

Actuator SSL และ observability

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

สำหรับ observability การขนส่ง 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) ช่วยให้จัดกลุ่ม service หลายตัวอย่างมีเหตุผลใน observability dashboard

OCI image ที่เบาลง

ตัวสร้าง OCI image เริ่มต้นเปลี่ยนจาก paketobuildpacks/builder-jammy-base เป็น paketobuildpacks/builder-jammy-java-tiny ซึ่งสร้าง image ที่เล็กลงอย่างมาก

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 ใหม่ทำให้การ build ข้ามแพลตฟอร์มสำหรับ 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>

image ที่ถูก optimize เหล่านี้เริ่มต้นเร็วขึ้นและใช้ทรัพยากรน้อยลง เป็นประโยชน์อย่างยิ่งสำหรับการ deploy บน Kubernetes

หมายเหตุการย้ายระบบ

การเปลี่ยน builder อาจส่งผลกระทบต่อแอปพลิเคชันที่พึ่งพาเครื่องมือระบบบางอย่าง ควรทดสอบ image ใหม่ก่อน deploy ไปยัง production

การเปลี่ยนแปลงใน Bean Validation

Spring Boot 3.4 ปรับพฤติกรรมการ validate ให้สอดคล้องกับข้อกำหนด Bean Validation การ validate จะไม่ส่งต่อไปยัง property ที่ซ้อนกันโดยอัตโนมัติอีกต่อไป

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 ในตำแหน่งที่การ validate ควรส่งต่อ

การเลิกใช้ @MockBean และ @SpyBean

annotation @MockBean และ @SpyBean ของ Spring Boot ถูกเลิกใช้เพื่อเปลี่ยนไปใช้ annotation ใหม่ของ 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");
    }
}

annotation เดิมยังใช้งานได้แต่จะแสดงคำเตือนการเลิกใช้ ควรวางแผนย้ายไปใช้ @MockitoBean และ @MockitoSpyBean

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

สรุป

Spring Boot 3.4 มอบการปรับปรุงที่สำคัญด้านประสิทธิภาพการพัฒนาและ observability:

รายการตรวจสอบการย้ายระบบ:

  • เปิดใช้งาน structured logging เพื่อ observability ที่ดีขึ้น
  • ตรวจสอบพฤติกรรม graceful shutdown (เปิดใช้งานเป็นค่าเริ่มต้นแล้ว)
  • พิจารณา virtual threads สำหรับแอปพลิเคชันที่มี I/O สูง (Java 21+)
  • ย้ายไปใช้ MockMvcTester เพื่อ test ที่อ่านง่ายขึ้น
  • เพิ่ม @Valid บน property @ConfigurationProperties ที่ซ้อนกัน
  • แทนที่ @MockBean/@SpyBean ด้วย @MockitoBean/@MockitoSpyBean
  • ทดสอบ OCI image ใหม่ก่อน deploy ไปยัง production

Spring Boot 3.4 เสริมความแข็งแกร่งในฐานะ framework อ้างอิงสำหรับการพัฒนา Java สมัยใหม่ โดยให้ความสำคัญเป็นพิเศษกับมาตรฐาน observability และประสิทธิภาพ

แหล่งข้อมูล:

แท็ก

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

แชร์

บทความที่เกี่ยวข้อง