스프링 부트 3.2 또는 스프링 프레임워크 6.1 이상 사용 시 생성자 바인딩이 안 될 때, -parameters 컴파일러 옵션을 추가하여 해결할 수 있다.

1. 문제

1.1. 증상

생성자 바인딩(Constructor binding)이 매개변수 이름을 가져오지 못함.

1.2. 발생한 조건

  1. 스프링 부트 3.2 또는 스프링 프레임워크 6.1 이상이면서,
  2. 인텔리제이를 사용하고,
  3. 인텔리제이 내장 빌드 도구(IntelliJ IDEA native builder)로 빌드하는 경우.
    • 즉, Gradle 설정에서 “Build and run using”을 IntelliJ IDEA로 바꾼 경우.

1.3. 콘솔에 출력된 에러 메시지

Cannot resolve parameter names for constructor [DTO 구현한 클래스]

2. 해결법

2.1. 자바 컴파일러에 -parameters 옵션 추가

  1. 인텔리제이 내장 빌드 도구 사용하는 경우: Settings > Java Compiler 검색 > Additional command line parameters 입력칸에 -parameters 추가

  2. Gradle - Kotlin 사용하는 경우: build.gradle에 다음 코드 추가
     tasks.withType<JavaCompile> {
         options.compilerArgs.add("-parameters")
     }
    
  3. Gradle - Groovy 사용하는 경우: build.gradle에 다음 코드 추가
     tasks.withType(JavaCompile).configureEach {
         options.compilerArgs.add("-parameters")
     }
    
  4. Maven 사용하는 경우: pom.xml에 다음 코드 추가
     <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
         <configuration>
             <parameters>true</parameters>
         </configuration>
     </plugin>
    

2.2. 그래도 안된다면?

  • Rebuild를 해보자.
    • 인텔리제이 내장 빌드 도구(IntelliJ IDEA native builder)는 기본적으로 증분 빌드를 수행한다. 증분 빌드(Incremental build)란 이전에 변경되지 않은 사항들을 제외하고 컴파일함으로써, 빠른 빌드 속도를 보장한다. Gradle도 마찬가지.
    • 인텔리제이는 순수 자바/코틀린 프로젝트에 대해 내장 빌드 도구가 증분 빌드를 통해 더 빠른 빌드를 지원한다고 한다. 얼마나 빠른지에 대한 통계는 안 보인다.
    • Rebuild는 증분 빌드하지 않고 프로젝트 전체를 다시 빌드한다.
    • 인텔리제이: Build > Rebuild Project

3. 원인

3.1. 클래스 삭제

정확한 원인은 스프링 부트 3.2와 스프링 프레임워크 6.1 릴리즈 노트에서 찾을 수 있었다.

  • 스프링 부트 3.2 릴리즈 노트 내용 중

    The version of Spring Framework used by Spring Boot 3.2 no longer attempts to deduce parameter names by parsing bytecode. If you experience issues with dependency injection or property binding, you should double check that you are compiling with the -parameters option.

스프링 부트는 3.2부터 더 이상 바이트코드를 파싱할때 파라미터 이름을 추론하지 않는다고 한다. 그리고 의존성 주입 또는 속성을 바인딩 할 때는 -parameters 옵션으로 컴파일 했는지 꼭 확인하라고 한다.

  • 스프링 프레임워크 6.1 릴리즈 노트 내용 중

    LocalVariableTableParameterNameDiscoverer has been removed in 6.1.

스프링 부트의 이러한 변화는 스프링 프레임워크 6.1부터 LocalVariableTableParameterNameDiscoverer가 제거된 것에 대한 대응이다.

3.2. 메소드 매개변수 이름 처리의 변천사

그렇다면 LocalVariableTableParameterNameDiscoverer는 무엇이고, 어째서 제거되었으며, -parameters 옵션은 귀찮게 왜 필요한걸까? 대략적인 타임라인으로 정리해봤다.

  1. 자바8 이전: 메소드 매개변수 이름을 바이트코드에 보존할 방법이 없었다.
    • 매개변수의 이름을 보존할 수 없으므로, 개발자들은 매개 변수의 이름이 아닌 순서에 의존해서 코드를 작성해야 했다.
  2. 스프링 프레임워크 2.0(2008년) 출시: LocalVariableTableParameterNameDiscoverer 클래스를 통해서 매개변수 이름을 쉽게 추출할 수 있게 했으며, 로컬 변수 테이블에서 매개변수의 이름들을 찾아내는 방식이다.
    • 바이트코드는 기본적으로 메소드 매개변수 이름을 보존하지 않지만, 로컬 변수 테이블은 매개변수 이름을 가지고 있다. 그리고 로컬 변수 테이블은 디버깅 정보에 포함되어 있고, 디버깅 정보는 바이트코드에 포함할 수 있기 때문에 가능했다고.
    • 하지만 디버깅 정보에 의존해야 했고, 바이트코드를 파싱함으로써 성능 저하가 일어날 수 있었으며, 그마저도 불일치 하는 경우가 있었다고 한다.
  3. 자바8 출시(2014년): 자바 컴파일러에 -parameters 옵션을 주면 바이트코드에 매개변수 이름을 포함하는 기능이 도입 되었다.
    • 이에 따라서, 스프링 프레임워크는 리플렉션 API를 이용해 직접 바이트코드에 명시된 매개변수 이름을 추출할 수 있게 하는 StandardReflectionParameterNameDiscoverer 클래스를 제공했다.
  4. 스프링 프레임워크 6.1 출시(2023년): StandardReflectionParameterNameDiscoverer에 의해서 완벽히 대체할 수 있지만 레거시를 위해 남겨두었던 LocalVariableTableParameterNameDiscoverer를 삭제했고, 스프링 부트 3.2는 이에 대응해 바이트코드 파싱 시에 매개변수 이름을 추론하는 기능을 제거했다.

3.3. 인텔리제이 내장 빌드 도구에서만 문제가 생기는 이유

  • Gradle의 경우에는 org.springframework.boot 플러그인, Maven의 경우에는 spring-boot-starter-parent 부모 프로젝트를 사용하고 있다면 직접 컴파일러 옵션을 추가해주지 않아도 자동으로 처리된다.
  • 각각은 Spring Initializer로 프로젝트를 만들면 빌드 설정 파일에 자동으로 추가되어 있으므로, Gradle과 Maven으로 빌드하면 일반적으로는 관련 문제가 발생하지 않을 것이라고 생각된다.

4. 개인 로그

4.1. 서론

길벗 코딩자율학습 스프링부트3의 예제를 따라가며 코딩 해보고 있다.

예제는 JDK 17, 스프링부트 3.1.0을 사용하고 있다.

난 JDK 17, 스프링부트 3.2.3로 프로젝트를 시작했다.

버전이 다르면 예제와 동일하게 진행되지 않을 수 있다는 걸 알지만, 말 안 듣는 놈은 꼭 있다.

이유는 최신 버전이 쓰고 싶었다. 문제 있겠어?

4.2. 문제 발생

게시판에 새 글을 게시하기 위한 @PostMapping 메소드를 작성하고 서버를 켰다. 새 글을 게시하려고 하면 브라우저 화면과 IDE 콘솔에 에러가 출력됐다.

Cannot resolve parameter names for constructor public com.example.firstproject.dto.ArticleForm(java.lang.String,java.lang.String)

엥? 벌써 막힌다고? 아직 그대로 따라한 것 밖에 없는데?

에러 메시지에 따르면, 생성자의 매개변수 이름을 처리하는 데 뭔가 문제가 있고, 그 문제는 DTO를 구현한 클래스에서 발생한 모양이었다.

4.3. 해결 과정

일단 길벗 네이버 카페를 찾아서 검색을 해봤으나 스프링 부트 관련 글이 많지 않아서 도움이 되지 않았다.

에러 메시지대로 DTO 클래스의 기존 생성자를 주석처리해봤다. 그리고 기본 생성자를 만들었다.

여전히 내가 쓴 글은 가져오지 못했지만, 최소한 null 값을 받아왔다.

에러 메시지로 구글링해보니 자바 컴파일러에 -parameters 옵션을 추가해주면 해결된다고 한다.

해결책은 찾는 건 별 일이 아니었는데, 왜 그런지 찾아서 정리하는건 예상보다 시간 꽤나 걸렸다.

참고 및 출처

태그:

카테고리:

에 마지막으로 수정됨

댓글남기기