JDK 21, Spring Boot 3.4로 구성된 API Server를 버전업하기 전, 주요 변화를 파악합니다.
아래 내용은 JDK17, Spring Boot 3.0 이상으로 버전업을 진행했다는 가정 하에 설명합니다.
JDK 11, Spring Boot 2.7 이하라면, Spring Boot 3.0 Migration Guide를 참고하시길 바랍니다.
버전업 목표
구분 | AS-IS | TO-BE |
---|---|---|
JDK | 17 | 21 |
Spring Boot | 3.0.13 | 3.4.x |
Spring Cloud | 2022.0.4 | 미정. (2023.0.5 / 2024.0.0) |
JDK 17 ~ 21까지의 총 주요 변화 및 기능을 설명하며, 각 버전별 세부 내용을 생략합니다.
플랫폼 스레드란?
java.lang.Thread
)라고 부르는 OS에서 생성한 스레드를 매핑해 JVM에서 사용가상 스레드란?
기존 코드와의 호환성
Spring Boot 3.2.0 이하일 경우
// Web Request를 처리하는 Tomcat이 Virtual Thread를 사용하여 유입된 요청을 처리하도록 함
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
return protocolHandler -> protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
}
// Async Task에 Virtual Thread 사용
@Bean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)
public AsyncTaskExecutor asyncTaskExecutor() {
return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
}
Spring Boot 3.2.0 이후
spring:
threads:
virtual:
enabled: true # 가상 스레드를 활성화
가상 스레드에 대한 기본 개념 및 성능 비교는 아래 기술 블로그들로 대체한다.
기존 동시성을 사용한 코드
Future<Shelter> shelter;
Future<List<Dog>> dogs;
// ThreadPool의 크기가 3인 Executor 생성
try (ExecutorService executorService = Executors.newFixedThreadPool(3)) {
// get shelter
shelter = executorService.submit(this::getShelter);
// get dogs
dogs = executorService.submit(this::getDogs);
Shelter theShelter = shelter.get(); // Join the shelter
List<Dog> theDogs = dogs.get(); // Join the dogs
Response response = new Response(theShelter, theDogs);
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
}
위 코드에서 getShelter()가 실행되는 동안 getDocs()에서 예외가 발생할 경우의 플로우
Structured Concurrency를 사용
// ShutdownOnFailure()은 해당 scope에서 문제가 발생할 경우 하위 작업을 모두 종료하는 생성자
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<Shelter> shelter = scope.fork(this::getShelter);
Future<List<Dog>> dogs = scope.fork(this::getDogs);
scope.join();
Response response = new Response(shelter.resultNow(), dogs.resultNow());
// ...
}
기타 사용법
// 예외를 전파
scope.throwIfFailed(e -> new APIException(400, "ERROR_CODE", "ERROR_MESSAGE"));
// 마감일 설정
scope.joinUntil(Instant.now().plusSeconds(1));
interface SequencedCollection<E> extends Collection<E> {
// new method
SequencedCollection<E> reversed();
// methods promoted from Deque
void addFirst(E);
void addLast(E);
E getFirst();
E getLast();
E removeFirst();
E removeLast();
}
_
)로 표시public class UnnamedPatternExample {
public static void main(String[] args) {
final int total = 0;
final int LIMIT = 3;
final List<String> strings = List.of("1", "2", "3");
// Unnamed Variable
for (String _ : strings) {
if (total < LIMIT) {
System.out.println("UnnamedPatternExample.main");
}
}
}
}
class Framework {
private final static ScopedValue<FrameworkContext> CONTEXT
= ScopedValue.newInstance(); // (1)
void serve(Request request, Response response) {
var context = createContext(request);
ScopedValue.where(CONTEXT, context) // (2)
.run(() -> Application.handle(request, response));
}
public PersistedObject readKey(String key) {
var context = CONTEXT.get(); // (3)
var db = getDBConnection(context);
db.readKey(key);
}
...
}
주요 변화로 정리된 내용은 개인 관심사 및 기술 스택이 반영되어있습니다.
spring.kafka.retry.topic.enabled: true
@GraphQlExceptionHandler
with @ControllerAdvice
)management.tracing.enabled=false
로 설정하지 않는 한, SpanId, TraceId를 로그에 기록logging:
pattern:
correlation: "[${spring.application.name:},%X{traceId:-},%X{spanId:-}] "
include-application-name: false
spring.threads.virtual.enabled=true
io.micrometer:micrometer-registry-prometheus
의존성을 제거 후io.micrometer:micrometer-registry-prometheus-simpleclient
를 추가simpleclient
에 대한 자동 구성을 제공하지만, deprecated되어 Spring Boot 3.5.0 버전에서 제거될 예정.AsyncTaskExecutor
is available in the context, it is now registered on the websocket ChannelRegistration
.HttpURLConnection
(SimpleClientHttpRequestFactory
)HttpClient
(JdkClientHttpRequestFactory
)spring.http.client.factor=??
http-components
, jetty
, reactor
, jdk
, simple
이 가능AS-IS : 중첩된 속성에 자동으로 @Valid
가 적용되어 검증이 동작.
@Validated
@ConfigurationProperties(prefix = "app")
public class AppProperties {
@NotBlank
private String name;
@NotNull
private DatabaseProperties database; // 중첩된 속성
public static class DatabaseProperties {
@NotBlank
private String url;
@NotBlank
private String username;
}
}
TO-BE : @Valid
를 추가해야만 검증이 이루어지도록 제어 가능
@Validated
@ConfigurationProperties(prefix = "app")
public class AppProperties {
@NotBlank
private String name;
@Valid // 중첩된 속성에 대해 검증을 전파하기 위해 추가
@NotNull
private DatabaseProperties database; // 중첩된 속성
public static class DatabaseProperties {
@NotBlank
private String url;
@NotBlank
private String username;
}
}
immediate
graceful
server.shutdown=immediate
@MockBean
, @SpyBean
를 사용할 수 없음.@MockitoBean
, @MockitoSpyBean
로 교체가 필요.Actuator Gateway API 추가 (Github Issue Link)
/actuator/gateway/refresh
/actuator/gateway/routes
/actuator/gateway/routefilters
/actuator/gateway/routepredicates
management.endpoint.gateway.enabled=true # default value
management.endpoints.web.exposure.include=gateway
feign.autoconfiguration.jackson.enabled
기본값 변경true
로 설정 ⇒ Jackson 자동 설정을 기본적으로 활성화feign
spring.cloud.openfeign
This release is based upon Spring Boot 3.2.0
spring-cloud-starter-gateway-mvc
spring-cloud-starter-gateway
Following Modules updated.
4.1.0
(issues)4.1.0
(issues)4.1.0
(issues)spring.cloud.gateway.route-refresh-listener.enabled=true
Following Modules updated.
4.1.1
(issues)4.1.2
(issues)4.1.1
(issues)영향받는 변경 사항 및 주목 할만한 신규 기능 없음으로 판단.
Following Modules updated.
4.1.2
(issues)4.1.4
(issues)4.1.2
(issues)This release is based on Spring Boot 3.2.7 it primarily contains bugfixes and dependency upgrades.
Following Modules updated.
4.1.3
(issues)4.1.5
(issues)4.1.3
(issues)This release of Spring Cloud is based on Spring Boot 3.2.12 and 3.3.6.
Updated to use HttpHeaders#headerSet
where appropriate.
public class CustomHeaderFilter implements GatewayFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 요청의 헤더를 가져오기
HttpHeaders headers = exchange.getRequest().getHeaders();
// 'headerSet'을 사용하여 커스텀 헤더 추가
HttpHeaders modifiedHeaders = new HttpHeaders();
modifiedHeaders.putAll(headers); // 기존 헤더를 유지
**modifiedHeaders.set("X-Custom-Gateway-Header", "GatewayHeaderValue");**
...
}
}
Following Modules updated.
4.1.4
(issues)4.1.6
(issues)4.1.4
(issues)This is mostly a bugfix and dependency upgrade release.
Following Modules updated.
4.1.5
(issues)This release is based upon Spring Boot 3.4.0 and Spring Framework 6.2.0.
enabled
플래그를 추가하여 해당 라우트를 활성화 / 비활성화 가능Following Modules updated.
4.2.0
(issues)4.2.0
(issues)4.2.0
(issues)