Spring Boot 3.4: 모든 새로운 기능 상세 설명

Spring Boot 3.4는 네이티브 구조화 로깅, 확장된 가상 스레드, 기본 그레이스풀 셧다운, MockMvcTester를 제공합니다. 새로운 기능의 완벽 가이드.

Spring Boot 3.4 새로운 기능과 개선 사항

Spring Boot 3.4는 2024년 11월에 출시되어 개발자 생산성과 애플리케이션 성능에서 상당한 개선을 제공합니다. 이 버전은 네이티브 구조화 로깅을 도입하고, 그레이스풀 셧다운을 기본으로 활성화하며, 프레임워크 전반에 걸쳐 가상 스레드 지원을 확장합니다.

전제 조건

Spring Boot 3.4는 최소 Java 17이 필요하며, 가상 스레드를 위해 Java 21을 지원합니다. 이 버전은 Spring Framework 6.2를 사용합니다.

네이티브 구조화 로깅

구조화 로깅은 애플리케이션 관측성에서 중요한 진전을 나타냅니다. 파싱하기 어려운 텍스트 기반 로그 대신, Spring Boot 3.4는 Elasticsearch, Grafana Loki, Datadog 같은 도구에서 사용할 수 있는 JSON 형식의 로그를 생성합니다.

세 가지 형식이 네이티브로 지원됩니다: 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 객체가 됩니다. 이 구조는 모니터링 플랫폼에서의 검색과 집계를 단순화합니다.

기본으로 활성화된 그레이스풀 셧다운

중요한 변경: 그레이스풀 셧다운이 이제 기본으로 활성화됩니다. 진행 중인 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.
        };
    }
}

이 개선으로 대부분의 경우 추가 설정 없이 무중단 배포 구현이 간소화됩니다.

확장된 가상 스레드 지원

Spring Boot 3.4는 가상 스레드 지원(Java 21+)을 더 많은 컴포넌트로 확장합니다. OtlpMeterRegistry와 Undertow 서버가 활성화 시 가상 스레드를 사용합니다.

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

이 단일 속성으로 애플리케이션의 스레딩 모델이 변환됩니다. 각 HTTP 요청이 자체 가상 스레드를 받아 스레드 풀을 고갈시키지 않고 수천 개의 동시 연결이 가능해집니다.

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

가상 스레드는 I/O 집약적인 애플리케이션에서 빛을 발합니다: HTTP 호출, 데이터베이스 쿼리, 파일 작업. OS 스레드는 대기 시간 동안 해제되고 재개 시 재할당됩니다.

호환성

가상 스레드는 최소 Java 21이 필요합니다. Java 17에서는 이 속성이 무시되고 기존 동작이 적용됩니다.

개선된 RestClient와 RestTemplate

Spring Boot 3.4는 HTTP 클라이언트 설정을 표준화합니다. HttpRequestFactory 선택이 이제 클래스패스 기반의 명확한 우선순위를 따릅니다.

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

이 접근 방식은 기본 HTTP 구현과 관계없이 일관된 API로 최대한의 유연성을 제공합니다.

Spring Boot 면접 준비가 되셨나요?

인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.

유창한 테스트를 위한 MockMvcTester

Spring Boot 3.4는 MockMvc의 AssertJ 기반 대안인 MockMvcTester를 도입합니다. 이 새로운 접근 방식은 테스트를 더 읽기 쉽고 표현력 있게 만듭니다.

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가 있을 때 자동 설정됩니다(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과 관측성

새로운 /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 전송이 이제 HTTP 외에 gRPC를 지원합니다.

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 지원 중단

Spring Boot의 @MockBean@SpyBean 어노테이션이 새로운 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는 생산성과 관측성에서 상당한 개선을 제공합니다:

마이그레이션 체크리스트:

  • 더 나은 관측성을 위해 구조화 로깅 활성화
  • 그레이스풀 셧다운 동작 확인 (이제 기본 활성화)
  • I/O 집약적 애플리케이션에 가상 스레드 고려 (Java 21+)
  • 더 읽기 쉬운 테스트를 위해 MockMvcTester로 마이그레이션
  • 중첩된 @ConfigurationProperties 속성에 @Valid 추가
  • @MockBean/@SpyBean@MockitoBean/@MockitoSpyBean으로 교체
  • 프로덕션 배포 전 새 OCI 이미지 테스트

Spring Boot 3.4는 관측성 표준과 성능에 특별한 관심을 기울이며 현대 Java 개발의 레퍼런스 프레임워크로서의 입지를 강화합니다.

참고 자료:

태그

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

공유

관련 기사