발단
Request 클래스의 Primitive type으로 선언된 변수에 대하여 Jackson Deserialize가 정상적으로 처리되지 않음
How to Deserializing?
Spring에서 @ReuqestBody를 사용하는 경우
body에 있는 data를 HttpMessageConverter 이용,
특히 Json Data의 경우 MappingJackson2HttpMessageConverter 통하여 Jackson ObjectMapper readValue() 호출하고,
그 결과 개발자가 지정한 Request Object를 만들어 반환한다.
요약하자면 결국 JSON 요청 데이터를 Java Reflection을 사용하여 개발자가 지정한 Request Object에 매핑해준다.
- obj는 개발자가 지정한 Request Object
- value는 deserialize 된 request data 중 일부
예제 코드
@RestController
public class PrimitiveTestController {
@PostMapping(value = "test/primitive")
public ResponseEntity<String> finalTest(@RequestBody final Request request){
final long number = request.number;
final Long number2 = request.number2;
return ResponseEntity.ok().body(request.toString());
}
}
class Request {
public static final long DEFAULT = 100L;
public final long number = DEFAULT;
public final Long number2 = DEFAULT;
}
호출 결과
Request{number=10, number2=10} 을 기대하였지만 결과는 위와 같다 😨
Why?
결과로 기대하였던 것은 number, number2 모두 Request Body로 Deserealize 된 Request{number=10, number2=10} 이다.
하지만 왜 예상한 결과대로 처리되지 않았을까? 🤔
final로 초기화된 원시 타입
Chapter 4. Types, Values, and Variables
A variable of primitive type or type String, that is final and initialized with a compile-time constant expression, is called a constant variable.
Chapter 13. Binary Compatibility
References to fields that are constant variables are resolved at compile time to the constant value that is denoted. No reference to such a field should be present in the code in a binary file (except in the class or interface containing the field, which will have code to initialize it). Such a field must always appear to have been initialized the default initial value for the type of such a field must never be observed.
final로 초기화 된 원시 타입은 complie-time에 inline code화 된다?
무슨 말인지 모르겠으니 다시 예시 코드를 봐보자 🥱
PrimitiveTestController.java
@RestController
public class PrimitiveTestController {
@PostMapping(value = "test/primitive")
public ResponseEntity<String> finalTest(@RequestBody final Request request){
final long number = request.number;
final Long number2 = request.number2;
return ResponseEntity.ok().body(request.toString());
}
}
제일 처음 보았던 Controller의 Java 코드이다.
이 코드를 컴파일 한 .class 파일을 확인해보자!
PrimitiveTestController.class 🙀
@RestController
public class PrimitiveTestController {
public PrimitiveTestController() {
}
@PostMapping({"test/primitive"})
public ResponseEntity<String> finalTest(@RequestBody final Request request){
request.getClass();
long number = 100L;
Long number2 = request.number2;
return ResponseEntity.ok().body(request.toString());
}
}
long number = 100L;
원래 코드의 의도는 Json Deserialize 된 Request Object의 number를 반환하는 것이었다.
final long number = request.number;
하지만 컴파일된 코드에서는 위와 같이 초기화에 사용된 constant value가 코드에 들어가 버렸다.
또한 Wrapper Class로 사용된 number2의 경우 primitive type과 별개로 의도하고자 했던 대로 컴파일되었다.
정리
- Primitive type, String의 경우 final & initialized 된다면 compile-time에 상수 표현식이 inline code화 된다.
- 그러므로 primitive type & String에 대하여 final 키워드를 사용한다면 해당 변수가 초기화되는 시점을 잘 고려해야 할 것이다.
참고
'자바 개발자되기' 카테고리의 다른 글
Open Session In View와 트랜잭션, 그리고 영속성 컨텍스트 (0) | 2021.03.24 |
---|---|
Java Ratelimiter - 초 당 처리량 조절하기 (1) | 2021.03.01 |
Spring @Transactional 어노테이션은 왜 Bean이 아닌 객체에서 적용되지 않는가? (0) | 2020.12.31 |
Junit5 @BeforeEach private method가 동작하는 이유 (0) | 2020.12.30 |
Junit5 - @BeforeEach @BeforeAll (0) | 2020.12.23 |