애노테이션 기반 컨트롤러를 처리하는 RequestMappingHandlerAdapter
는
ArgumentResolver
를 호출해서 컨트롤러(핸들러)가 필요로 하는 다양한 파라미터의 값(객체)을 생성한다.
스프링 부트 기본 메시지 컨버터
0 = ByteArrayHttpMessageConverter
1 = StringHttpMessageConverter
2 = MappingJackson2HttpMessageConverter
byte[]
, 미디어타입: */*
,String
, 미디어타입: */*
객체
또는 HashMap
, 미디어타입 application/json
관련Jackson의 ObjectMapper는 Java Object ←→ JSON 파싱을 쉽게 해주는 클래스 입니다.
ObjectMapper 클래스를 살펴보면, serialize와 deserialize라는 단어가 포함된 메서드가 많이 있습니다.
Java 오브젝트를 Json으로 파싱하는 것을 직렬화(serialize)라고 하며, 반대는 역직렬화(deserialize)라고 합니다.
jackson은 java.lang reflection 라이브러리를 사용한다.
구체적인 클래스 타입을 알지 못해도, 그 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API
자바에서 제공하는 리플렉션(Reflection)은 C, C++과 같은 언어를 비롯한 다른 언어에서는 볼 수 없는 기능이다. 이미 로딩이 완료된 클래스에서 또 다른 클래스를 동적으로 로딩(Dynamic Loading)하여 생성자(Constructor), 멤버 필드(Member Variables) 그리고 멤버 메서드(Member Method) 등을 사용할 수 있도록 한다.
java Reflection이 가져올 수 없는 정보 중 하나가 바로 생성자의 인자 정보들이다. 따라서 기본 생성자 없이 파라미터가 있는 생성자만 존재한다면 java Reflection이 객체를 생성할 수 없게 되는 것이다.
Spring에서 기본생성자가 없어도 Object Mapping이 정상적으로 동작한다.
JDK 8 이후 생성자의 인자 이름을 알 수 있게 되었고, 그런 일을 하는 모듈이 추가되었다.
인수가 1개인 생성자(및 정적 팩터리 메서드)는 모호하기 때문에 바인딩에는 2가지 가능한 "모드"가 있습니다.
참고
참고
속성 기반(Properties-based) : 입력은 JSON 객체여야 하며, 속성은 생성자 인자와 이름으로 일치해야 합니다(따라서 이름은 위와 같이 바이트 코드이거나 주석이 있어야 합니다); 속성 값은 일치하는 인수가 나타내는 유형으로 역직렬화됩니다. 이 모드는 인자 수에 관계없이 모든 생성자에서 작동합니다.
위임(Delegating) : 입력은 모든 JSON 유형이 될 수 있으며, 생성자 인자 하나(유일한)의 유형으로 역직렬화됩니다(이름은 상관없지만 유형은 일치해야 함). 이 모드는 인수가 1개인 생성자에서만 작동합니다(실제로 @JacksonInject를 사용하는 특수한 경우가 있으며 정확한 규칙은 "주입되지 않은 매개변수가 하나뿐이어야 한다"는 것입니다).
// accepts JSON value like: "some text"
// and serializes back to JSON String
public class StringWrapper {
private String value;
@JsonCreator(mode = JsonCreator.MODE.DELEGATING)
public StringWrapper(String v) {
value = v;
}
// commonly serialized like so:
@JsonValue
public String getValue() {
return value;
}
}
// accepts JSON value like: { "id" : "xc974" }
public class IdToken {
private String id;
@JsonCreator(mode = JsonCreator.MODE.PROPERTIES)
public IdToken(@JsonProperty(id") String id) {
this.id = id;
}
// will serialize as JSON object with this property:
public String getId() { return id; }
}
즉 인자가 하나인경우 JSON value 가 some text
와 같이 String-value
단순 문자열인지 {"name" : "value"}
인지 모호하다는 뜻인것 같다..
@JsonCreator(mode = JsonCreator.MODE.PROPERTIES)
를 명시해준다.Jackson은 JSON 필드의 이름을 Java 오브젝트의 getter 및 setter 메소드와 일치시켜 JSON 오브젝트의 필드를 Java 오브젝트의 필드에 매칭합니다. Jackson은 getter 및 setter 메소드 이름의 "get"및 "set"부분을 제거하고 나머지 이름의 첫 문자를 소문자로 변환합니다.
예를 들어 brand라는 JSON 필드는 getBrand () 및 setBrand ()라는 Java getter 및 setter 메소드와 일치합니다. engineNumber라는 JSON 필드는 getEngineNumber () 및 setEngineNumber ()라는 getter 및 setter와 일치합니다.
동일한 이름의 변수를 직접 찾는 것이 아니라, Getter와 Setter를 통해 찾아서 매칭한다는 것입니다.
reflection을 사용해서 값을 필드에 주입해주므로 Setter는 필요하지 않다.
Request 에서는 Deserialize시 Getter가 없어도 객체를 생성및 필드 할당이 가능하다.
Response 에서 Serialize시 Getter가 필요하다.
Response 만 Breaking Point에 걸린다.
이외에도 intellij build
또는 @JsonProperty
, @JsonCreator
등에 어노테이션에 따라서 결과가 달라진다.
위 내용을 전부 생각하면서 개발하는것은 비효율적이다.
다음과 같이 DTO 규칙을 정해서 기본생성자와 Getter를 모두 만들어주는것이 좋은것같다.
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class ItemResponse {
private final Long id;
private final String name;
@JsonProperty("image-url")
private final String imageUrl;
private final int price;
}
기본 생성자가 필요한 이유
@RequestBody에 ArgumentResolver(아규먼트 리졸버)가 동작하지 않는 이유
Jackson ObjectMapper에서 기본 생성자 없이 Deserialization 하기
ObjectMapper의 동작 방식과 SpringBoot가 제공하는 추가 기능들
https://bbbicb.tistory.com/46
https://velog.io/@wisdom08/RequestBody%EB%A1%9C-String-%ED%83%80%EC%9E%85%EC%9D%84-%EB%B0%9B%EB%8A%94-%EA%B2%BD%EC%9A%B0