spring-boot 3.1.10 -> 3.2.5 로 마이그레이션 하는 시나리오입니다.
보시다시피 spring-boot 3.1.x는 오픈소스 EoL이 2024-05-18입니다.
(글을 발행한 시점이 2024-04-20 이어서 오늘 날짜로 빗금이 있네요)
spring-boot 오픈소스 버전 EoL 주기가 짧기 때문에 미리 미리 버전업을 해야할 것 같습니다. 만약 취약점이 발견되는 경우 치명적인 문제가 생길 수 있기 때문입니다.
공식 업그레이드 가이드
다들 공식 업그레이드 가이드를 정독하시길 바랍니다.
Gradle
build.gradle.kts
plugins {
java
groovy
idea
checkstyle
jacoco
id("org.springframework.boot") version "3.2.5" // Modified
id("io.spring.dependency-management") version "1.1.4" // Modified
id("com.google.cloud.tools.jib") version "3.4.1"
id("org.sonarqube") version "4.4.1.3373"
}
...
// Properties
extra["springCloudVersion"] = "2023.0.1" // Modifyed
KotlinCompile Error
1. ErrorAttributes.ERROR_ATTRIBUTE 이 존재하지 않음
@Nullable
@Override
public Throwable getError(WebRequest webRequest) {
Throwable exception = getAttribute(webRequest, ERROR_INTERNAL_ATTRIBUTE);
if (exception == null) {
exception = getAttribute(webRequest, RequestDispatcher.ERROR_EXCEPTION);
}
if (exception == null) {
return null;
}
// webRequest.setAttribute(ErrorAttributes.ERROR_ATTRIBUTE, exception, RequestAttributes.SCOPE_REQUEST); // 컴파일 오류가 발생해서 제거: 필요하지 않음.
return exception;
}
Kotlin관련해서 spring-boot 에 등록된 이슈를 살펴보면 기존 레거시 코드 찌꺼기여서 제거 요망.
Ref: https://github.com/spring-projects/spring-boot/issues/38907
Deprecated
1. MethodArgumentNotValidException#resolveErrorMessages
/**
* Resolve global and field errors to messages with the given
* {@link MessageSource} and {@link Locale}.
* @return a Map with errors as keys and resolved messages as values
* @since 6.0.3
* @deprecated in favor of using {@link BindErrorUtils} and
* {@link #getAllErrors()}, to be removed in 6.2
*/
@Deprecated(since = "6.1", forRemoval = true)
public Map<ObjectError, String> resolveErrorMessages(MessageSource messageSource, Locale locale) {
return BindErrorUtils.resolve(getAllErrors(), messageSource, locale);
}
JavaBindErrorUtils
과 getAllErrors()
가 spirng-web-mvc 6.2 에서 제거될 예정이라고 함.
보통 Deprecated 에는 대안을 지정해주는데, 여기에서는 그런 것도 없음.
private List<FieldErrorView> getFieldErrorViews(MethodArgumentNotValidException ex, Locale locale) {
Map<ObjectError, String> messageMap =
// ex.resolveErrorMessages(messageSource, locale); AS-IS
BindErrorUtils.resolve(ex.getAllErrors(), messageSource, locale); // TO-BE
return ex.getFieldErrors().stream()
.map(f -> new FieldErrorView(f, messageMap.get(f)))
.toList();
}
Java우선 위처럼 변경했는데, 효과가 있을지….
Spring Initialization Error
1. java.lang.IllegalStateException: @TransactionalEventListener method must not be annotated with @Transactional unless when declared as REQUIRES_NEW or NOT_SUPPORTED
이것을 Spring Initialization 시 검증해주는 것을 좋다고 봄.@TransactionalEventListener
를 사용하는 경우 phase
가 AFTER_COMMIT
인 경우에는 새로운 트랜잭션이 필요하기 때문에 꼭 REQUIRES_NEW
로 트랜잭션이 생성되게 선언해주어야함.
반대로 phase
가 BEFORE_COMMIT
인 경우에는 어짜피 트랜잭션 범위 안에 있으니 @Transactional
을 제거하도록 하고 있음.(굳이 유지해도 될 것 같은데, 제거하게 예외가 발생됨.)
@TransactionalEventListener(value = OpenChattingProfileSaved.class, phase = TransactionPhase.BEFORE_COMMIT)
// @Transactional 제거 요망: BEFORE_COMMIT 이기 때문에 존재하는 트랜잭션 내에서 실행하는 불필요한 @Transactional 애노테이션이 있어서 예외가 발생하고 있었음.
public void subscribeOpenChattingProfileSaved(OpenChattingProfileSaved event) {
KakaoId kakaoId = event.getKakaoId();
agree(Kind.OPEN_CHATTING_PERSONAL_INFO, kakaoId);
}
JavaRef: https://bin-repository.tistory.com/167
Test
1. 400 BadRequest 기본 메시지 변경
AS-IS
: “title: '공백일 수 없습니다'
“TO-BE
: “공백일 수 없습니다.”
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.err").value("요청 본문이 유효하지 않습니다. errors를 참고하세요."))
.andExpect(jsonPath("$.errors.length()").value(6))
.andExpect(jsonPath("$.errors[?(@.field == 'title')].message")
// 변경 전
//.value("title: '공백일 수 없습니다'"))
// 변경 후
.value("title: '공백일 수 없습니다'"))
Java