Json 맵핑 원리
@ReqeustBody나 Jackson을 이용해 Object를 맵핑할 때 어노테이션을 쓰는 경우도 있고, getter/setter로 맵핑되게 구성하는 경우도 있다.
맵핑을 가능하게 하는 케이스들을 알아보자
1. 어노테이션 (getter/setter 없이)
@JsonProperty, @JsonAutoDetect 사용
2. ObjectMapper 설정
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
3. getter/setter 설정
하지만 이 케이스들을 중복해서 사용할 경우 데이터가 여러개 들어갈 수 있으니 조심해야한다.
예를들면 @JsonProperty를 설정하고, getter/setter를 설정하면 값이 중복해 들어간다.
ObjectMapper readValue(역직렬화 JSON -> Object) method를 통해 분석해보았다. (jackson 2.7.3 기준)
1. getter / setter는 어떻게 직렬화, 역직렬화를 할까?
직렬화: getter, 역직렬화: setter를 사용한다.
그런데 사실 setter없이 getter만으로도 직렬화, 역직렬화가 가능하다.
2. setter 없이 값을 어떻게 주입할까?
소스코드를 보면 java.lang.reflect 패키지를 통해 직접 주입한다.
public final class FieldProperty
extends SettableBeanProperty
{
...
/**
* Actual field to set when deserializing this property.
* Transient since there is no need to persist; only needed during
* construction of objects.
*/
final protected transient Field _field;
@Override
public final void set(Object instance, Object value) throws IOException
{
try {
_field.set(instance, value);
} catch (Exception e) {
// 15-Sep-2015, tatu: How coud we get a ref to JsonParser?
_throwAsIOE(e, value);
}
}
}
3. 직렬화는?
직렬화는 writeValue method를 통해 알아 볼 수 있다.
4. 어떻게 ObjectMapper를 기반으로 @ReqeustBody Object로 맵핑이 연결될까?
Spring에서 HttpMessageConverter가 이 역할을 해주고, 나는 converter 중 MappingJackson2HttpMessageConvertert를 사용하였다.
dispatcher-servlet.xml에서 설정 가능하다.
[dispatcher-servlet.xml]
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
소스를 보면 ObjectMapper를 사용하고있다.
public class MappingJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {
private String jsonPrefix;
public MappingJackson2HttpMessageConverter() {
this(Jackson2ObjectMapperBuilder.json().build());
}
public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
super(objectMapper, new MediaType[]{MediaType.APPLICATION_JSON_UTF8, new MediaType("application", "*+json", DEFAULT_CHARSET)});
}
public void setJsonPrefix(String jsonPrefix) {
this.jsonPrefix = jsonPrefix;
}
public void setPrefixJson(boolean prefixJson) {
this.jsonPrefix = prefixJson ? ")]}', " : null;
}
protected void writePrefix(JsonGenerator generator, Object object) throws IOException {
if (this.jsonPrefix != null) {
generator.writeRaw(this.jsonPrefix);
}
String jsonpFunction = object instanceof MappingJacksonValue ? ((MappingJacksonValue)object).getJsonpFunction() : null;
if (jsonpFunction != null) {
generator.writeRaw("/**/");
generator.writeRaw(jsonpFunction + "(");
}
}
protected void writeSuffix(JsonGenerator generator, Object object) throws IOException {
String jsonpFunction = object instanceof MappingJacksonValue ? ((MappingJacksonValue)object).getJsonpFunction() : null;
if (jsonpFunction != null) {
generator.writeRaw(");");
}
}
}
AbstractJackson2HttpMessageConverter 소스를 분석해보면 objectMapper의 readValue method를 이용한다는 것을 알 수 있다.
@Override
public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
JavaType javaType = getJavaType(type, contextClass);
return readJavaType(javaType, inputMessage);
}
@SuppressWarnings("deprecation")
private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) {
try {
if (inputMessage instanceof MappingJacksonInputMessage) {
Class<?> deserializationView = ((MappingJacksonInputMessage) inputMessage).getDeserializationView();
if (deserializationView != null) {
return this.objectMapper.readerWithView(deserializationView).withType(javaType).
readValue(inputMessage.getBody());
}
}
return this.objectMapper.readValue(inputMessage.getBody(), javaType);
}
catch (IOException ex) {
throw new HttpMessageNotReadableException("Could not read document: " + ex.getMessage(), ex);
}
}