Notice
Recent Posts
Recent Comments
Link
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Tags
more
Archives
Today
Total
관리 메뉴

DevYGwan

HttpMessageConverter vs ObjectMapper 비교 본문

Study/Spring

HttpMessageConverter vs ObjectMapper 비교

YGwan 2023. 4. 13. 03:22

 

 

그렇다면 이번에는 HttpMessageConverter와 ObjectMapper를 비교해보도록 하겠습니다. 이 둘을 비교하는 이유는 둘다 json, 객체 사이에 변환과 관련되어있기 때문입니다.

 

먼저 앞에서 말한 HttpMessageConverter의 내용을 다시 한번 설명하자면,

  • HttpMessageConverter는 Spring MVC에서 요청과 응답의 데이터 형식 변환을 처리하는 인터페이스입니다. 
  • 즉, 요청 본문과 응답 본문의 내용을 자바 객체로 변환하거나, 자바 객체를 요청 본문과 응답 본문으로 변환할 때 사용됩니다.

 

해당 HttpMessageConverter의 구현부를 살펴보자면

public interface HttpMessageConverter<T> {

	/**
	 * Indicates whether the given class can be read by this converter.
	 * @param clazz the class to test for readability
	 * @param mediaType the media type to read (can be {@code null} if not specified);
	 * typically the value of a {@code Content-Type} header.
	 * @return {@code true} if readable; {@code false} otherwise
	 */
	boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);

	/**
	 * Indicates whether the given class can be written by this converter.
	 * @param clazz the class to test for writability
	 * @param mediaType the media type to write (can be {@code null} if not specified);
	 * typically the value of an {@code Accept} header.
	 * @return {@code true} if writable; {@code false} otherwise
	 */
	boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

	/**
	 * Return the list of media types supported by this converter. The list may
	 * not apply to every possible target element type and calls to this method
	 * should typically be guarded via {@link #canWrite(Class, MediaType)
	 * canWrite(clazz, null}. The list may also exclude MIME types supported
	 * only for a specific class. Alternatively, use
	 * {@link #getSupportedMediaTypes(Class)} for a more precise list.
	 * @return the list of supported media types
	 */
	List<MediaType> getSupportedMediaTypes();

	/**
	 * Return the list of media types supported by this converter for the given
	 * class. The list may differ from {@link #getSupportedMediaTypes()} if the
	 * converter does not support the given Class or if it supports it only for
	 * a subset of media types.
	 * @param clazz the type of class to check
	 * @return the list of media types supported for the given class
	 * @since 5.3.4
	 */
	default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
		return (canRead(clazz, null) || canWrite(clazz, null) ?
				getSupportedMediaTypes() : Collections.emptyList());
	}

	/**
	 * Read an object of the given type from the given input message, and returns it.
	 * @param clazz the type of object to return. This type must have previously been passed to the
	 * {@link #canRead canRead} method of this interface, which must have returned {@code true}.
	 * @param inputMessage the HTTP input message to read from
	 * @return the converted object
	 * @throws IOException in case of I/O errors
	 * @throws HttpMessageNotReadableException in case of conversion errors
	 */
	T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException;

	/**
	 * Write an given object to the given output message.
	 * @param t the object to write to the output message. The type of this object must have previously been
	 * passed to the {@link #canWrite canWrite} method of this interface, which must have returned {@code true}.
	 * @param contentType the content type to use when writing. May be {@code null} to indicate that the
	 * default content type of the converter must be used. If not {@code null}, this media type must have
	 * previously been passed to the {@link #canWrite canWrite} method of this interface, which must have
	 * returned {@code true}.
	 * @param outputMessage the message to write to
	 * @throws IOException in case of I/O errors
	 * @throws HttpMessageNotWritableException in case of conversion errors
	 */
	void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
			throws IOException, HttpMessageNotWritableException;

}

위와 같습니다. 해당 내용을 간단하게 설명하자면,

  • canRead(), canWrite() : 메시지 컨버터가 데이터의 클래스 타입과 미디어 타입을 지원하는지 확인
  • read(), write() : 확인 후 지원한다면, 메시지를 적절한 데이터로 변환(읽기, 쓰기)

 

 

스프링 부트는 다양한 타입의 메서지 컨버터를 지원하며 대표적인 메세지 컨버터는
- ByteArrayHttpMessageConverter(우선순위 0)
- StringHttpMessageConverter(우선순위 1)
- MappingJackson2HttpMessageConverter(우선순위 2)
이렇게 3가지 입니다.

 

해당 부분중 이번에 자세하게 다룰 메세지 컨버터는 "MappingJackson2HttpMessageConverter" 입니다. 해당 메세지 컨버터는 후에 자세하게 다루도록 하겠습니다. 그렇다면 이제부터 ObejctMapper에 대해서 설명해보도록 하겠습니다.

 

 

 


 

ObjectMapper

  • JSON 형식을 사용할 때, 응답들을 직렬화하고 요청들을 역직렬화 할 때 사용하는 기술입니다.
  • 즉, JSON 데이터와 Java 객체 간의 변환을 수행하는 기능을 제공합니다.
  • Jackson 라이브러리의 클래스입니다.
  • JSON 데이터를 Java 객체로 변환할 때는, ObjectMapper 클래스의 readValue() 메서드를 사용합니다.
  • Java 객체를 JSON 데이터로 변환할 때는, ObjectMapper 클래스의 writeValue() 메서드를 사용합니다.

정리하자면, ObjectMapper는 JSON 데이터와 자바 객체 간의 변환을 쉽게 수행할 수 있도록 도와주는 클래스로써, JSON 데이터와 자바 객체 간의 연동성을 높이는게 도움을 주는 도구입니다.

그래서 ObjectMapper를 공부하다보니 @RequestBody의 HttpMessageConverter와 ObjectMapper 클래스가 비슷한 역할을 한다고 생각이 들었습니다. 그래서 해당 부분을 정리해보려고 합니다.

 


 

일단 결론부터 말하자면 

HttpMessageConverter하고 Objectmapper는 밀접한 관련이 있다.

 

입니다. 그러면 어떤식으로 관련이 있는지 살펴보도록 하겠습니다.

 

기본적으로 Spring은 HttpMessageConverter를 사용하여 Java 객체와 HTTP 요청/응답 간의 변환 작업을 처리하고, ObjectMapper를 사용하여 Java 객체와 JSON 데이터 간의 변환 작업을 처리합니다. 이를 통해 Spring은 Java 객체와 JSON 데이터 간의 변환을 간편하게 처리할 수 있습니다. 이로인해 HttpMessageConverter와 ObjectMapper는 Spring에서 RESTful 웹 서비스 구축에 매우 중요한 역할을 합니다.

 처음에는 ObjectMapper는 HttpMessageConverter 인터페이스를 구현한 구현체라고 생각했습니다. 하지만 해당 내용은 잘못된 예측이였습니다. 그래서 저는 이 두개의 관계가 어떻게 되는지 궁금하였습니다. 이를 알기 위해서는 일단, 서버에서의 JSon 형식 데이터 처리 과정에 대해서 알아야 합니다.

 


 

서버의 요청 처리 방법

 클라이언트에서 서버로 전송되는 데이터는 대개 JSON 형식으로 전송됩니다. 이를 그냥 사용하기에는 불편한 점이 많아 Spring에서는 이를 Java 객체로 변환되어 서버에서 처리합니다. 이때, Spring에서는 이러한 변환 작업을 HttpMessageConverter 인터페이스를 통해 처리하며, 이중에서도 대표적으로 Jackson 라이브러리의 ObjectMapper 클래스가 사용됩니다. 따라서 Spring에서는 클라이언트에서 전송된 JSON 데이터를 HttpMessageConverter를 통해 ObjectMapper에게 전달하여 Java 객체로 변환하고, 이후에는 이 Java 객체를 서비스 계층에서 처리하는 것이 일반적인 방법입니다. 여기서 HttpMessageConverter을 통한다고 하였는데 이에 대한 구현 방법은, HttpMessageConverter 인터페이스를 구현한 구현체 중 하나인 MappingJackson2HttpMessageConverter 클래스에서 ObjectMapper를 사용하여 JSON 데이터를 Java 객체로 변환하거나( 주로 @RequestBody) Java 객체를 JSON 데이터로 변환하는(주로 @ResponseBody)로 변환하는 작업을 처리합니다.

 

MappingJackson2HttpMessageConverter는 Spring MVC에서 기본적으로 사용되는 HttpMessageConverter 중 하나이며, @RequestBody 어노테이션이 적용된 메소드에서 JSON 데이터를 자바 객체로 변환하는 역할을 수행합니다. 또한, @ResponseBody 어노테이션이 적용된 메소드에서 자바 객체를 JSON 데이터로 변환하여 응답 본문으로 반환하는 역할도 수행합니다. 이때 해당 클래스에서는 Jackson 라이브러리의 ObjectMapper 클래스를 사용하여 JSON 데이터를 Java 객체로 변환하거나 Java 객체를 JSON 데이터로 변환합니다. 

 

그렇다면, 위의 내용을 MappingJackson2HttpMessageConverter 구현부에서 살펴보도록 하겠습니다.

 

 

public abstract class AbstractJackson2HttpMessageConverter extends AbstractGenericHttpMessageConverter<Object> {
	...
}

public abstract class AbstractGenericHttpMessageConverter<T> extends AbstractHttpMessageConverter<T>
		implements GenericHttpMessageConverter<T> {
        	...
}

public abstract class AbstractHttpMessageConverter<T> implements HttpMessageConverter<T> {
	...
}

위의 보이는 코드에서와 같이, 해당 클래스를 타고 타고 올라가면 결국 HttpMessageConverter 인터페이스와 관련이 있다는 것을 확인할 수 있었습니다. 또한,

 

위의 사진에서 보이는 바와 같이 해당 클래스에 ObjectMapper 클래스가 사용되고 있는 것을 확인할 수 있었습니다.

 

앞에서 말한 바와 같이, 해당 클래스는 HttpMessageConverter 인터페이스를 구현한 구현체이고, 이를 읽는 것은 read() 메서드가 담당하고 있는 것을 알 수 있습니다. 그렇다면 해당 함수의 구현부를 찾고 이와 ObjectMapper와 관계가 있는지를 확인해보도록 하겠습니다.

 

 

MappingJackson2HttpMessageConverter의 read() 구현부

@Override
	public Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
			throws IOException, HttpMessageNotReadableException {

		JavaType javaType = getJavaType(type, contextClass);
		return readJavaType(javaType, inputMessage);
	}
    
    private Object readJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
		MediaType contentType = inputMessage.getHeaders().getContentType();
		Charset charset = getCharset(contentType);

		ObjectMapper objectMapper = selectObjectMapper(javaType.getRawClass(), contentType);
		Assert.state(objectMapper != null, "No ObjectMapper for " + javaType);

		boolean isUnicode = ENCODINGS.containsKey(charset.name()) ||
				"UTF-16".equals(charset.name()) ||
				"UTF-32".equals(charset.name());
		try {
			if (inputMessage instanceof MappingJacksonInputMessage) {
				Class<?> deserializationView = ((MappingJacksonInputMessage) inputMessage).getDeserializationView();
				if (deserializationView != null) {
					ObjectReader objectReader = objectMapper.readerWithView(deserializationView).forType(javaType);
					if (isUnicode) {
						return objectReader.readValue(inputMessage.getBody());
					}
					else {
						Reader reader = new InputStreamReader(inputMessage.getBody(), charset);
						return objectReader.readValue(reader);
					}
				}
			}
			if (isUnicode) {
				return objectMapper.readValue(inputMessage.getBody(), javaType);
			}
			else {
				Reader reader = new InputStreamReader(inputMessage.getBody(), charset);
				return objectMapper.readValue(reader, javaType);
			}
		}
		catch (InvalidDefinitionException ex) {
			throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
		}
		catch (JsonProcessingException ex) {
			throw new HttpMessageNotReadableException("JSON parse error: " + ex.getOriginalMessage(), ex, inputMessage);
		}
	}

 

코드가 복잡한데, 이를 간단하게 설명하자면 결국 read 메서드 부분은 ObjectMapper 객체를 사용하고 해당 객체의 readValue()를 사용한다는 것을 알 수 있었습니다. write()부분 또한 ObjectMapper 객체를 사용하고 해당 객체의 writeValue()를 사용합니다.

 

 


 

최종적으로 정리하자면, 

 

@RequestBody, @ResponseBody 둘다 결국 응답/요청들을 직렬화/역직렬화해야하고, 이때 사용하는 것이 HttpMessageConverter 인터페이스의 구현체입니다. 해당 구현체는 크게 3가지 종류가 있는데, 이중 JSON 데이터와 관련된 메세지 컨버터는 MappingJackson2HttpMessageConverter입니다. 해당 컨버터를 통해 JSON 형식의 데이터를 Java 객체로 변환하거나, Java 객체를 JSON 형식으로 변환합니다. 또한 해당 부분의 구현부를 살펴보면 ObjectMapper를 통해 직렬화 / 역직렬화가 구현된 것을 확인할 수 있었습니다. 아래는 해당 부분을 간단하게 도식화 한 그림입니다.