본문 바로가기

Kotlin

코틀린과 Hibernate, CGLIB, Proxy 오해와 재대로된 사용법 - (6)

5장에서 Kotlin 에서 @RequsetBody 에 기본 생성자가 필요없다라는 것에 대해 알아봤습니다.

 

이 과정에서 찜찜한 상태로 넘어갔었습니다.

 

이번에 새롭게 알게된 것으로 이를 해결했기에 기록해봅니다.

 

 

1. Jackson-Module-Kotlin

 

https://proandroiddev.com/parsing-optional-values-with-jackson-and-kotlin-36f6f63868ef

 

Parsing Optional Values with Jackson and Kotlin

A quick introduction to Jackson Kotlin module for parsing data with missing values

proandroiddev.com

이전 글에서, Json 을 @RequestBody에서 코틀린으로 된 Dto 로 변환할때, kotlinValueInstantiator 라는 것이 있었습니다.

 

이 클래스가 바로 Jackson Module Kotlin 안에 있었습니다.

 

이 모듈은 본인은 Intellij 의 Spring Initializer 를 이용하여 Spring Web Mvc 를 생성하면 자동으로 의존성이 추가되었습니다.

 

 

위의 포스팅을 보면 Jackson Kotlin Module 을 사용하는 이유는 아래입니다.

  1. Data Class 는 빈 생성자를 빈 생성자를 가질 수 없다.
  2. Primary Constructor 를 사용한 일반 Class 는 빈 생성자가 없다.

따라서 아래의 조치를 취해야 했던 겁니다.

  1. Primary Constructor 를 사용하지 않은 Class 사용. (빈 생성자가 자동으로 생기게)
  2. @JsonProperty 를 명시적으로 붙이기.

 

이를 개선한 방향으로 Jackson Module Kotlin 을 사용하는 것입니다.

이 모듈 덕분에 Kotlin 은 코틀린의 Primary 생성자를 인식하고 사용할 수 있게 됩니다.

 

 

 

https://github.com/FasterXML/jackson-module-kotlin/blob/master/README.md

https://github.com/FasterXML/jackson-module-kotlin

 

 

 

 

 

 

 

2. Jackson-Module-Parameter-names

하지만 의문점은 풀리지 않았습니다. 프로젝트에서 위의 Jackson-Module-Kotlin 의존성을 제거하고 돌려봐도 기본생성자 없이 정상적으로 작동했기 때문입니다.

 

디버그 해보니 위의 경우와 다르게 kotlinValueInstantiator 없이도 잘 돌아갔습니다. (모듈 의존성이 없어졌으니 당연...)

 

다른 무언가가 있나 하고 파보니, 위의 모듈이 정답이었습니다.

 

 

2-1. JDK 8 의 Reflection API

https://stackoverflow.com/questions/21455403/how-to-get-method-parameter-names-in-java-8-using-reflection/21455958#21455958

 

How to get Method Parameter names in Java 8 using reflection?

Java 8 has the ability to acquire method parameter names using Reflection API. How can I get these method parameter names? As per my knowledge, class files do not store formal parameter names. How...

stackoverflow.com

 

http://openjdk.java.net/jeps/118

 

위의 내용에 따르면 JDK 8 버전 이전에는 Reflection 으로 파라미터 이름을 가져올 수 없었으나, JDK 8 부터 해당 기능이 추가되었다고 합니다.

 

해당 API 를 사용하는 방법은 컴파일 타임에 컴파일 옵션으로 -parameters 옵션을 주면 되며, Spring Boot Gradle Plugin 에서 컴파일시 자동으로 붙여준다고 합니다.

 

 

2-2. Jackson-Module-Parameter-names

https://github.com/FasterXML/jackson-modules-java8

 

GitHub - FasterXML/jackson-modules-java8: Set of support modules for Java 8 datatypes (Optionals, date/time) and features (param

Set of support modules for Java 8 datatypes (Optionals, date/time) and features (parameter names) - GitHub - FasterXML/jackson-modules-java8: Set of support modules for Java 8 datatypes (Optionals,...

github.com

 

Spring boot 의 Web 의존성을 그대로 사용하면 spring boot stater 에서 Json Deserialize 에 Jacson 라이브러리가 사용됨을 잘 알고 있을 것입니다.

 

JacksonAutoConfiguration 클래스를 보면 다음의 내용이 정의되어 있습니다.

 

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ObjectMapper.class)
public class JacksonAutoConfiguration {

	...

	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass(ParameterNamesModule.class)
	static class ParameterNamesModuleConfiguration {

		@Bean
		@ConditionalOnMissingBean
		ParameterNamesModule parameterNamesModule() {
			return new ParameterNamesModule(JsonCreator.Mode.DEFAULT);
		}

	}
    
    ...
    
}

 

parameterNames 라는 모듈이 Jackson 에 등록되어 사용되네요.

 

그럼 Parameter Names 모듈을 들여다 봅시다.

 

package com.fasterxml.jackson.module.paramnames;

class ParameterExtractor implements java.io.Serializable {
    private static final long serialVersionUID = 1L;

    public Parameter[] getParameters(Executable executable) {
        return executable.getParameters();
    }
}

 

executable.getParameters() 로 파라미터들을 얻어오는 로직을 발견할 수 있습니다.

 

Executable 을 타고 들어가면, Java 1.8 부터 생성된 Consturctor 정보를 얻는 Reflection API 라고 합니다.

 

package java.lang.reflect;

/**
 * A shared superclass for the common functionality of {@link Method}
 * and {@link Constructor}.
 *
 * @since 1.8
 */
public abstract class Executable extends AccessibleObject
    implements Member, GenericDeclaration {

 

 

결과적으로, Jackson-Kotlin-Module 없이도 Java Reflection 을 통해 빈 생성자 없이 @RequestBody Deserialize 를 진행할 수 있다 라고 알 수 있습니다.

 

 

 

 

정리해보면,

Jackson-Kotlin-Module 은 Spring Initializer 를 통해 Spring Web MVC 를 생성하면 자동으로 추가됩니다. 따라서 build.gradle 에서 의존성을 삭제하면 뺄 수 있습니다.

반면, Jackson-Module-Parameter-names 모듈은 Spring-Boot-Starter-Web 안의 Jackson 에 묶여있는 모듈이므로, 추가라기 보다는 기본적으로 들어오는 스타터 모듈입니다.

 

두 모듈 모두, 코틀린에서 기본 생성자 없이 (@RequestBody 등에서) Deserialize 를 진행할 수 있게 해줍니다.

 

두 모듈이 모두 존재할 경우, Jackson-Kotlin-Module 이 우선적으로 사용됩니다.

 

 

 

 

*추가*

https://github.com/FasterXML/jackson-modules-java8/tree/master/parameter-names

마지막을 보면 

if class Person has a single argument constructor, its argument needs to be annotated with @JsonProperty("propertyName"). This is to preserve legacy behavior, see FasterXML/jackson-databind/#1498

이라는 문구가 있다.

 

https://github.com/FasterXML/jackson-databind/issues/1498

 

Allow handling of single-arg constructor as property based by default · Issue #1498 · FasterXML/jackson-databind

class Foo { private final Bar bar; Foo(Bar bar) { this.bar = bar; } } With the parameter-names-module enabled I'd expect this class to be correctly deserialized from {"bar":{...}}. Mo...

github.com

인수가 여러개인 생성자인 경우는 잘 동작했지만, 단일 인수를 가진 생성자인 경우 @JsonCreator 를 명시적으로 붙여야 했다가, 2020/9 월에 이 문제가 해결되어 닫힌 이슈다.

 

어 나는 최근에 기본 생성자 없으니 에러나던데 ? 라는 물음에는 인자가 하나이지 않았을까~ 그것도 현재는 해결이 되었지만 이라는 답을 할 수 있을 것 같다.

 

 

 

 

참고.

https://cheese10yun.github.io/spring-kotlin/

 

Kotlin으로 Spring 개발할 때 - Yun Blog | 기술 블로그

Kotlin으로 Spring 개발할 때 - Yun Blog | 기술 블로그

cheese10yun.github.io