1. 개요

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

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

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

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

2. HttpMessageConverter

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. Spring REST의 Kryo

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

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;
    }
}

공지 사항은 우리가 사용하고있는 의 ThreadLocal을 Kryo 인스턴스의 생성 비싼 얻을 수 있기 때문에 단순히 여기에, 우리는 우리가 할 수있는만큼이를 다시 사용하려고합니다.

컨트롤러 방법은 간단합니다 (사용자 지정 프로토콜 별 데이터 유형이 필요하지 않으며 일반 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);
}

이제 List에 Google Protocol Buffer도 추가한다고 가정 해 보겠습니다. 이 예제에서는 다음 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은 Protocol Buffer에 대한 내장 지원을 제공합니다. 작동하게 하려면 지원되는 변환기 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를 처리 하므로 을 구분하기 위해 컨트롤러에서 변경할 필요가 없음).

호출되는 메서드에 대한 모호성을 해결하는 방법에는 두 가지가 있습니다. 첫 번째 방법은 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 Tutorial에서이 바이너리 데이터 형식의 구현은 물론 Github에 있습니다. 이것은 Maven 기반 프로젝트이므로 그대로 가져 와서 실행하기 쉽습니다.