class, fun 키워드 앞에 open 키워드가 추가된 것을 확인할 수 있습니다. plugin.spring이 위의 어노테이션이 추가된 클래스에는 자동으로 open 키워드를 자동으로 추가합니다. Spring Initializr를 이용해서 프로젝트를 구성하면 plugin.spring 플러그인은 자동 적용됩니다.
Spring Boot 2.x 버전부터는 CGLIB Proxy 방식으로 Bean을 관리하고 있습니다. CGLIB Proxy는 Target Class를 상속받아 생성하기 때문에 open으로 상속이 가능한 상태이어야 합니다. 그러기 때문에 all-open 플러그인이 필요합니다.
no-arg
no-arg는 argument가 없는 기본 생성자를 의미합니다. 클래스는 기본 생성자가 기본적으로 생성되며 다른 생성자를 만들면 기본 생성자는 명시적으로 선언하지 않는 이상 사라지게 됩니다.
// 기본 생성자를 주석한 경우 publicfinalclassBarpublicconstructor(name: kotlin.String) { publicfinalval name: kotlin.String /* compiled code */ }
// 기본 생성자를 주석 하지 않는 경우 publicfinalclassBarpublicconstructor(name: kotlin.String) { publicconstructor() { /* compiled code */ } publicfinalval name: kotlin.String /* compiled code */ }
no-arg는 주로 plugin.jpa 플러그인과 같이 사용됩니다. kotlin-spring 플러그인과 마찬가지로 @Entity, @Embeddable, @MappedSuperclass 어노테이션에 자동으로 동작합니다.
@Test internalfun `lazy loading test`() { // 데이터 set up val order = orderRepository.save(Order("202012-12")) val book = bookRepository.save(Book(title = "title", order = order))
// 영속성 컨텍스트 초기화 entityManager.clear()
// lazy loading 이기 때문에 book에 대한 조회 쿼리는 발생하지 않을 것이라고 예상 val findBook = bookRepository.findByIdOrNull(book.id!!)!! }
Lazy 패치 전략을 사용했기 때문에 book에 대한 조회 시 order에 대한 조회가 쿼리가 발생하지 않을 것이라고 예상했지만 결과는 다릅니다.
order에 대한 조회 발생합니다. 디버깅 모드를 활용해서 해당 값을 확인해보겠습니다.
Lazy Loading이기 때문에 order는 Proxy 객체이어야 합니다. 하지만 Proxy 객체가 아니라 실제 order 객체를 가지고 있는 것을 확인할 수 있습니다. 그 이유는 Kotlin은 기본적으로 final이기 때문에 Proxy 클래스를 생성하지 못합니다. Proxy 클래스를 생성하기 위해서는 상속이 가능해야 하므로 open이 필요한데 없으니 Proxy 기반으로 Lazy Loading을 진행할 수 없는 것입니다. 그렇다면 allOpen을 적용하고 다시 테스트해보겠습니다.
Lazy Loading이 정상적으로 동작하고 Proxy 기반으로 order 객체를 가져오는것을 확인할 수 있습니다.
jackson-module-kotlin 모듈
Spring Initializr를 이용해서 Spring Web MVC 프로젝트를 생성하게 되면 com.fasterxml.jackson.module:jackson-module-kotlin 디펜던시가 자동으로 추가된다. jackson-module-kotlin는 기존 Jackson으로 deserialize 하기 위해서는 기본 생성자가 반드시 필요합니다. 하지만 코틀린에서 data class의 객체를 deserialize를 진행하게 되면 기본 생성자가 없기 때문에 아래와 같은 예외가 발생합니다.
1
(no Creators, like default construct, exist): cannot deserialize from Object valu...
public SampleRequestBody(@NotNull String name, int age) { Intrinsics.checkNotNullParameter(name, "name"); super(); this.name = name; this.age = age; } .. }
코틀린의 data class는 all argument 생성자만 생성하기 때문에 기존 jackson으로 deserialize를 못하고 jackson-module-kotlin을 통해서 단일 생성자로 deserialize를 진행할 수 있습니다. 하지만 최근에는 이 부분도 개선되어 굳이 jackson-module-kotlin 모듈의 도움 없이 기본 생성자가 없이도 deserialize를 진행할 수 있습니다.
ParameterName 이용
spring-boot-starter-web의 디펜던시를 통해서 jackson-module-parameter-names는 자동으로 추가됩니다. 즉 ParameterName 모듈은 이미 사용할 수 있는 상태입니다. ParameterNameModule 모듈에 대한 자세한 정리는 Jackson으로 파싱한 JSON 속성값을 생성자로 전달하기에 잘 정리되어 있어 한 번 읽어 보시는 것을 권유 드립니다.
HttpMessageConverter 리스트에 기본으로 추가되는 Jackson에는 ParameterNamesModule이 추가되어 있습니다. 만약 WebMvcConfigurer 인터페이스를 기반으로 extendMessageConverters를 재정의 해서 사용하는 경우에는 ParameterNamesModule 설정을 추가해야 합니다. 결과 적으로 jackson-module-kotlin 모듈 없이 jackson-module-parameter-names 모듈만으로 deserialize를 진행할 수 있습니다.