1. 개요

JSON 및 XML은 REST API와 관련하여 널리 사용되는 데이터 전송 형식이지만 사용할 수 있는 유일한 옵션은 아닙니다.

다양한 수준의 직렬화 속도와 직렬화된 데이터 크기를 가진 다른 많은 형식이 있습니다.

이 기사에서는 Kryo로 설명 하는 이진 데이터 형식을 사용하도록 Spring REST 메커니즘을 구성하는 방법을 살펴봅니다 .

또한 Google 프로토콜 버퍼에 대한 지원을 추가하여 여러 데이터 형식을 지원하는 방법을 보여줍니다.

2. HttpMessage 변환기

HttpMessageConverter 인터페이스는 기본적으로 REST 데이터 형식 변환을 위한 Spring의 공용 API입니다.

원하는 변환기를 지정하는 방법에는 여러 가지가 있습니다. 여기에서  WebMvcConfigurer 를 구현하고 재정의된 configureMessageConverters 메서드  에서 사용하려는 변환기를 명시적으로 제공합니다 .

@Configuration
@EnableWebMvc
@ComponentScan({ "com.baeldung.web" })
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
        //...
    }
}

3. 크리오

3.1. Kryo 개요 및 Maven

Kryo는 텍스트 기반 형식에 비해 우수한 직렬화 및 역직렬화 속도와 더 작은 전송 데이터 크기를 제공하는 이진 인코딩 형식입니다.

이론상으로는 서로 다른 종류의 시스템 간에 데이터를 전송하는 데 사용할 수 있지만 주로 Java 구성 요소와 함께 작동하도록 설계되었습니다.

다음 Maven 의존성을 사용하여 필요한 Kryo 라이브러리를 추가합니다.

<dependency>
    <groupId>com.esotericsoftware</groupId>
    <artifactId>kryo</artifactId>
    <version>4.0.0</version>
</dependency>

kryo 의 최신 버전을 확인하려면 여기 를 참조하십시오 .

3.2. Kryo in Spring REST

Kryo를 데이터 전송 형식으로 활용하기 위해 Custom형 HttpMessageConverter 를 생성 하고 필요한 직렬화 및 역직렬화 논리를 구현합니다. 또한 Kryo: application/x-kryo 에 대한 사용자 지정 HTTP 헤더를 정의 합니다. 데모 목적으로 사용하는 완전히 단순화된 작업 예제는 다음과 같습니다.

public class KryoHttpMessageConverter extends AbstractHttpMessageConverter<Object> {

    public static final MediaType KRYO = new MediaType("application", "x-kryo");

    private static final ThreadLocal<Kryo> kryoThreadLocal = new ThreadLocal<Kryo>() {
        @Override
        protected Kryo initialValue() {
            Kryo kryo = new Kryo();
            kryo.register(Foo.class, 1);
            return kryo;
        }
    };

    public KryoHttpMessageConverter() {
        super(KRYO);
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        return Object.class.isAssignableFrom(clazz);
    }

    @Override
    protected Object readInternal(
      Class<? extends Object> clazz, HttpInputMessage inputMessage) throws IOException {
        Input input = new Input(inputMessage.getBody());
        return kryoThreadLocal.get().readClassAndObject(input);
    }

    @Override
    protected void writeInternal(
      Object object, HttpOutputMessage outputMessage) throws IOException {
        Output output = new Output(outputMessage.getBody());
        kryoThreadLocal.get().writeClassAndObject(output, object);
        output.flush();
    }

    @Override
    protected MediaType getDefaultContentType(Object object) {
        return KRYO;
    }
}

Kryo 인스턴스를 생성하는 데 비용이 많이 들 수 있기 때문에 여기 에서 ThreadLocal 을 사용하고 있으며 가능한 한 많이 이를 재사용하고 싶습니다.

컨트롤러 방법은 간단합니다(사용자 정의 프로토콜별 데이터 유형이 필요하지 않으며 일반 Foo DTO를 사용함).

@RequestMapping(method = RequestMethod.GET, value = "/foos/{id}")
@ResponseBody
public Foo findById(@PathVariable long id) {
    return fooRepository.findById(id);
}

그리고 우리가 모든 것을 올바르게 연결했는지를 증명하기 위한 빠른 테스트:

RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(Arrays.asList(new KryoHttpMessageConverter()));

HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(KryoHttpMessageConverter.KRYO));
HttpEntity<String> entity = new HttpEntity<String>(headers);

ResponseEntity<Foo> response = restTemplate.exchange("http://localhost:8080/spring-rest/foos/{id}",
  HttpMethod.GET, entity, Foo.class, "1");
Foo resource = response.getBody();

assertThat(resource, notNullValue());

4. 다중 데이터 형식 지원

동일한 서비스에 대해 여러 데이터 형식에 대한 지원을 제공하려는 경우가 많습니다. 클라이언트는 Accept HTTP 헤더 에서 원하는 데이터 형식을 지정하고 해당 메시지 변환기를 호출하여 데이터를 직렬화합니다.

일반적으로 다른 변환기를 등록하기만 하면 바로 사용할 수 있습니다. Spring은 Accept 헤더의 값과 변환기에서 선언된 지원되는 미디어 유형 을 기반으로 적절한 변환기를 자동으로 선택합니다 .

예를 들어 JSON과 Kryo에 대한 지원을 추가하려면 KryoHttpMessageConverterMappingJackson2HttpMessageConverter 를 모두 등록합니다 .

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
    messageConverters.add(new MappingJackson2HttpMessageConverter());
    messageConverters.add(new KryoHttpMessageConverter());
    super.configureMessageConverters(messageConverters);
}

이제 Google Protocol Buffer도 List에 추가한다고 가정해 보겠습니다. 이 예제에서는 다음 proto 파일 을 기반으로 protoc 컴파일러 로 생성된 FooProtos.Foo 클래스가 있다고 가정 합니다.

package baeldung;
option java_package = "com.baeldung.web.dto";
option java_outer_classname = "FooProtos";
message Foo {
    required int64 id = 1;
    required string name = 2;
}

Spring은 프로토콜 버퍼에 대한 일부 내장 지원을 제공합니다. 작동하게 하려면 지원되는 변환기 List에 ProtobufHttpMessageConverter 를 포함하기만 하면 됩니다.

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
    messageConverters.add(new MappingJackson2HttpMessageConverter());
    messageConverters.add(new KryoHttpMessageConverter());
    messageConverters.add(new ProtobufHttpMessageConverter());
}

그러나 FooProtos.Foo 인스턴스 를 반환하는 별도의 컨트롤러 메서드를 정의해야 합니다(JSON과 Kryo는 둘 다 Foo s를 처리하므로 둘을 구분하기 위해 컨트롤러에서 변경할 필요가 없습니다).

어떤 메서드가 호출되는지에 대한 모호성을 해결하는 두 가지 방법이 있습니다. 첫 번째 접근 방식은 protobuf 및 기타 형식에 대해 서로 다른 URL을 사용하는 것입니다. 예를 들어 protobuf의 경우:

@RequestMapping(method = RequestMethod.GET, value = "/fooprotos/{id}")
@ResponseBody
public FooProtos.Foo findProtoById(@PathVariable long id) { … }

그리고 다른 사람들을 위해:

@RequestMapping(method = RequestMethod.GET, value = "/foos/{id}")
@ResponseBody
public Foo findById(@PathVariable long id) { … }

protobuf의 경우 value = "/fooprotos/{id}" 를 사용하고 다른 형식의 경우 value = "/foos/{id}" 를 사용 합니다.

두 번째이자 더 나은 접근 방식은 동일한 URL을 사용하지만 protobuf에 대한 요청 매핑에서 생성된 데이터 형식을 명시적으로 지정하는 것입니다.

@RequestMapping(
  method = RequestMethod.GET, 
  value = "/foos/{id}", 
  produces = { "application/x-protobuf" })
@ResponseBody
public FooProtos.Foo findProtoById(@PathVariable long id) { … }

생성 어노테이션 속성 에 미디어 유형을 지정하면 클라이언트가 제공한 Accept 헤더 의 값을 기반으로 매핑을 사용해야 하는 기본 Spring 메커니즘에 대한 힌트를 제공하므로 어떤 메서드가 필요한지에 대한 모호성이 없습니다. "foos/{id}" URL 에 대해 호출됩니다 .

두 번째 접근 방식을 사용하면 모든 데이터 형식에 대해 클라이언트에 균일하고 일관된 REST API를 제공할 수 있습니다.

마지막으로 Spring REST API와 함께 프로토콜 버퍼를 사용하는 데 더 깊이 관심이 있다면 참조 문서 를 살펴보십시오 .

5. 추가 메시지 변환기 등록

configureMessageConverters 메서드 를 재정의하면 모든 기본 메시지 변환기가 손실된다는 점에 유의하는 것이 매우 중요합니다 . 당신이 제공하는 것만 사용됩니다.

때로는 이것이 정확히 원하는 것이지만 많은 경우에 JSON과 같은 표준 데이터 형식을 이미 관리하는 기본 변환기를 계속 유지하면서 새 변환기를 추가하기를 원할 것입니다. 이렇게 하려면 extendMessageConverters 메서드를 재정의합니다.

@Configuration
@EnableWebMvc
@ComponentScan({ "com.baeldung.web" })
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void extendMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
        messageConverters.add(new ProtobufHttpMessageConverter());
        messageConverters.add(new KryoHttpMessageConverter());
    }
}

6. 결론

이 예제에서는 Spring MVC에서 어떤 데이터 전송 포맷을 사용하는 것이 얼마나 쉬운지 살펴보고 Kryo를 예로 들어 살펴보았다.

또한 서로 다른 클라이언트가 서로 다른 형식을 사용할 수 있도록 여러 형식에 대한 지원을 추가하는 방법도 보여 주었습니다.

Spring REST API 예제에서 이 이진 데이터 형식의 구현은 물론 Github에 있습니다. 이것은 Maven 기반 프로젝트이므로 그대로 가져오고 실행하기 쉬워야 합니다.

REST footer banner