728x90
발단
EndPoint 에서 RequestBody 가 아닌 ModelAttribute 를 통해 핸들링할 때 DTO 가 2-depth 가 되며 생긴 문제를 공유하고자 합니다.
예시 코드
@RestController
@RequestMapping("")
class TestController {
@PostMapping("")
fun hiPost(
@ModelAttribute body: TestDto,
){
...
}
}
data class TestDto(
var name: String = "",
var testInnerDto: TestInnerDto
)
data class TestInnerDto(
var age: Int
)
오류 발생
1차 시도
java.lang.NullPointerException: Parameter specified as non-null is null: method com.test.TestDto.<init>, parameter testInnerDto
요약 : non-null 로 선언한 파라미터가 null 이기 때문에 NullPointerException 발생
즉, TestDto 를 deserialize 하는 과정에서 testInnerDto 의 값이 입력되지 못해 NullPointerException 이 발생하였다.
2차 시도 - TestDto.testInnerDto 를 nullable 타입으로 수정
java.lang.NoSuchMethodException: com.test.TestInnerDto.<init>()
요약 : TestInnerDto 의 비어있는 생성자가 없으니 NoSuchMethodException 발생
생성자가 없다고 하는 것을 보니 reflection 을 통해 TestInnerDto 를 구성하려 시도하였지만 비어있는 생성자가 없어 오류가 발생한 듯 보인다.
- 특수한 경우를 제외하고 Kotlin 의 Data Class 는 기본 생성자를 따로 제공해주지 않습니다.
해결
해결책1
@RestController
@RequestMapping("")
class TestController {
@PostMapping("")
fun hiPost(
@ModelAttribute body: TestDto,
){
...
}
}
data class TestDto(
var name: String = "",
var testInnerDto: TestInnerDto?
)
class TestInnerDto{
var age: Int = 0
constructor(){
}
constructor(age: Int){
this.age = age
}
}
- TestDto.testInnerDto 를 nullable type 으로 수정
- TestInnerDto 를 Data Class -> Class 일반 클래스로 수정
- TestInnerDto 에 대해 비어있는 생성자 구현
설명
- TestDto 의 비어있는 생성자가 없어 발생한 오류에 대한 책임을 TestInnerDto 로 전가
- TestInnerDto 의 구조를 Data Class -> Class 로 변경하고 deserialize 를 위한 기본 생성자를 생성
해결책2
...
data class TestDto(
var name: String = "",
var testInnerDto: TestInnerDto = TestInnerDto()
)
...
- TestDto.testInnerDto 에 대한 default value 설정
설명
- Kotlin 은 모든 변수가 default value 로 세팅되어 있을 경우 기본 생성자를 제공
- TestDto Deserialize 과정에서 기본 생성자를 통해 인스턴스 생성하여 사용
결론
2-depth 이상을 @ModelAttribute 로써 다루고자 할 때 kotlin 에서는 방법이 위와 같은 방식을 사용해야 합니다.
혹여나 제가 모르는 해결책이 있다면 댓글로 링크나 설명부탁드립니다!
728x90