Conversores de mensagens HTTP com o Spring Framework
1. Visão geral
Este artigo descrevehow to Configure HttpMessageConverters in Spring.
Simplificando, podemos usar conversores de mensagens para ordenar e remover a ordenação de objetos Java de e para JSON, XML, etc. - através de HTTP.
Leitura adicional:
Negociação de conteúdo do Spring MVC
Um guia para configurar a negociação de conteúdo em um aplicativo Spring MVC e ativar e desativar as várias estratégias disponíveis.
Retornando dados de imagem / mídia com o Spring MVC
O artigo mostra as alternativas para retornar a imagem (ou outra mídia) com o Spring MVC e discute os prós e contras de cada abordagem.
Formatos de dados binários em uma API REST do Spring
Neste artigo, exploraremos como configurar o mecanismo Spring REST para utilizar formatos de dados binários que ilustramos com o Kryo. Além disso, mostramos como oferecer suporte a vários formatos de dados com os buffers do Google Protocol.
2. O básico
2.1. Habilitar Web MVC
Para começar, o aplicativo da Web precisa serconfigured with Spring MVC support.. Uma maneira conveniente e muito personalizável de fazer isso é usar a anotação@EnableWebMvc:
@EnableWebMvc
@Configuration
@ComponentScan({ "com.example.web" })
public class WebConfig implements WebMvcConfigurer {
...
}
Observe que esta classe implementaWebMvcConfigurer - o que nos permitirá alterar a lista padrão de conversores Http com os nossos.
2.2. Os conversores de mensagem padrão
Por padrão, as seguintes instânciasHttpMessageConverters são pré-ativadas:
-
ByteArrayHttpMessageConverter - converte matrizes de bytes
-
StringHttpMessageConverter - converte Strings
-
ResourceHttpMessageConverter - converteorg.springframework.core.io.Resource para qualquer tipo de fluxo de octeto
-
SourceHttpMessageConverter - convertejavax.xml.transform.Source
-
FormHttpMessageConverter - converte os dados do formulário de / paraMultiValueMap<String, String>.
-
Jaxb2RootElementHttpMessageConverter - converte objetos Java de / para XML (adicionado apenas se JAXB2 estiver presente no caminho de classe)
-
MappingJackson2HttpMessageConverter - converte JSON (adicionado apenas se Jackson 2 estiver presente no caminho de classe) __
-
MappingJacksonHttpMessageConverter - converte JSON (adicionado apenas se Jackson estiver presente no caminho de classe)
-
AtomFeedHttpMessageConverter - converte feeds Atom (adicionado apenas se Rome estiver presente no caminho de classe)
-
RssChannelHttpMessageConverter - converte feeds RSS(added only if Rome is present on the classpath)
3. Comunicação cliente-servidor - somente JSON
3.1. Negociação de conteúdo de alto nível
Cada implementação deHttpMessageConverter tem um ou vários tipos MIME associados.
Ao receber uma nova solicitação,Spring will use the “Accept” header to determine the media type that it needs to respond with.
Em seguida, ele tentará encontrar um conversor registrado que seja capaz de lidar com esse tipo de mídia específico. Por fim, ele será usado para converter a entidade e enviar de volta a resposta.
O processo é semelhante ao recebimento de uma solicitação que contém informações JSON. A estrutura terá um cabeçalhouse the “*Content-Type ”para determinar o tipo de mídia do corpo da solicitação *.
Em seguida, ele pesquisará porHttpMessageConverter que possa converter o corpo enviado pelo cliente em um objeto Java.
Vamos esclarecer isso com um exemplo rápido:
-
o cliente envia uma solicitação GET para/foos com o cabeçalhoAccept definido comoapplication/json - para obter todos os recursosFoo como JSON
-
oFoo Spring Controller é atingido e retorna as entidades JavaFoo correspondentes
-
O Spring usa um dos conversores de mensagens Jackson para organizar as entidades em JSON
Vejamos agora os detalhes de como isso funciona - e como podemos aproveitar as anotações@ResponseBodye @RequestBody.
3.2. @ResponseBody
@ResponseBody em um método do Controlador indica ao Spring quethe return value of the method is serialized directly to the body of the HTTP Response. Conforme discutido acima, o cabeçalho “Accept” especificado pelo Cliente será usado para escolher o Conversor Http apropriado para empacotar a entidade.
Vejamos um exemplo simples:
@GetMapping("/{id}")
public @ResponseBody Foo findById(@PathVariable long id) {
return fooService.findById(id);
}
Agora, o cliente irá especificar o cabeçalho “Aceitar” paraapplication/json na solicitação - exemplo de comandocurl:
curl --header "Accept: application/json"
http://localhost:8080/spring-boot-rest/foos/1
A classeFoo:
public class Foo {
private long id;
private String name;
}
E o corpo de resposta HTTP:
{
"id": 1,
"name": "Paul",
}
3.3. @RequestBody
Podemos usar a anotação@RequestBody no argumento de um método Controller para indicarthat the body of the HTTP Request is deserialized to that particular Java entity. Para determinar o conversor apropriado, o Spring usará o cabeçalho “Content-Type” da solicitação do cliente.
Vejamos um exemplo:
@PutMapping("/{id}")
public @ResponseBody void update(@RequestBody Foo foo, @PathVariable String id) {
fooService.update(foo);
}
A seguir, vamos consumir isso com um objeto JSON - estamos especificando “Content-Type“ comoapplication/json:
curl -i -X PUT -H "Content-Type: application/json"
-d '{"id":"83","name":"klik"}' http://localhost:8080/spring-boot-rest/foos/1
Recebemos 200 OK - uma resposta bem-sucedida:
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Length: 0
Date: Fri, 10 Jan 2014 11:18:54 GMT
4. Configuração de conversores personalizados
Também podemoscustomize the message converters by implementing the WebMvcConfigurer interfacee sobrescrever o métodoconfigureMessageConverters:
@EnableWebMvc
@Configuration
@ComponentScan({ "com.example.web" })
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(
List> converters) {
messageConverters.add(createXmlHttpMessageConverter());
messageConverters.add(new MappingJackson2HttpMessageConverter());
}
private HttpMessageConverter
E aqui está a configuração XML correspondente:
Neste exemplo, estamos criando um novo conversor - oMarshallingHttpMessageConverter - e usando o suporte Spring XStream para configurá-lo. Isso permite uma grande flexibilidade desdewe’re working with the low-level APIs of the underlying marshalling framework - neste caso XStream - e podemos configurar isso como quisermos.
Observe que este exemplo requer adicionar a biblioteca XStream ao caminho de classe.
Também esteja ciente de que ao estender esta classe de suporte,we’re losing the default message converters which were previously pre-registered.
Podemos, é claro, agora fazer o mesmo para Jackson - definindo nosso próprioMappingJackson2HttpMessageConverter. Podemos agora definir umObjectMapper personalizado neste conversor e configurá-lo conforme necessário.
Neste caso, XStream foi a implementação de marshaller / unmarshaller selecionada, masothers comoCastorMarshaller também podem ser usados.
Neste ponto - com o XML ativado no back-end - podemos consumir a API com representações XML:
curl --header "Accept: application/xml"
http://localhost:8080/spring-boot-rest/foos/1
4.1. Suporte de inicialização por mola
Se estivermos usando Spring Boot, podemos evitar a implementação da areiaWebMvcConfigurer adicionando todos os conversores de mensagens manualmente, como fizemos acima.
Podemos apenas definir diferentesHttpMessageConverter beans no contexto, e Spring Boot irá adicioná-los automaticamente à configuração automática que ele cria:
@Bean
public HttpMessageConverter
5. UsandoRestTemplate do Spring com conversores de mensagem Http
Assim como no lado do servidor, a conversão de mensagens Http pode ser configurada no lado do cliente no SpringRestTemplate.
Vamos configurar o modelo com os cabeçalhos “Accept” e “Content-Type” quando apropriado. Em seguida, tentaremos consumir a API REST com empacotamento e desempacotamento completo do RecursoFoo - tanto com JSON quanto com XML.
5.1. Recuperando o Recurso sem CabeçalhoAccept
@Test
public void testGetFoo() {
String URI = “http://localhost:8080/spring-boot-rest/foos/{id}";
RestTemplate restTemplate = new RestTemplate();
Foo foo = restTemplate.getForObject(URI, Foo.class, "1");
Assert.assertEquals(new Integer(1), foo.getId());
}
5.2. Recuperando um Recurso com Cabeçalho de Aceitarapplication/xml
Vamos agora recuperar explicitamente o recurso como uma representação XML. Vamos definir um conjunto de conversores e colocá-los emRestTemplate.
Como estamos consumindo XML, usaremos o mesmo XStream marshaller de antes:
@Test
public void givenConsumingXml_whenReadingTheFoo_thenCorrect() {
String URI = BASE_URI + "foos/{id}";
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(getMessageConverters());
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_XML));
HttpEntity entity = new HttpEntity(headers);
ResponseEntity response =
restTemplate.exchange(URI, HttpMethod.GET, entity, Foo.class, "1");
Foo resource = response.getBody();
assertThat(resource, notNullValue());
}
private List> getMessageConverters() {
XStreamMarshaller marshaller = new XStreamMarshaller();
MarshallingHttpMessageConverter marshallingConverter =
new MarshallingHttpMessageConverter(marshaller);
List> converters =
ArrayList>();
converters.add(marshallingConverter);
return converters;
}
5.3. Recuperando um Recurso com Cabeçalho de Aceitarapplication/json
Da mesma forma, vamos consumir a API REST solicitando JSON:
@Test
public void givenConsumingJson_whenReadingTheFoo_thenCorrect() {
String URI = BASE_URI + "foos/{id}";
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(getMessageConverters());
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
HttpEntity entity = new HttpEntity(headers);
ResponseEntity response =
restTemplate.exchange(URI, HttpMethod.GET, entity, Foo.class, "1");
Foo resource = response.getBody();
assertThat(resource, notNullValue());
}
private List> getMessageConverters() {
List> converters =
new ArrayList>();
converters.add(new MappingJackson2HttpMessageConverter());
return converters;
}
5.4. Atualize um recurso com XMLContent-Type
Por fim, vamos também enviar dados JSON para a API REST e especificar o tipo de mídia desses dados por meio do cabeçalhoContent-Type:
@Test
public void givenConsumingXml_whenWritingTheFoo_thenCorrect() {
String URI = BASE_URI + "foos/{id}";
RestTemplate restTemplate = new RestTemplate();
restTemplate.setMessageConverters(getMessageConverters());
Foo resource = new Foo(4, "jason");
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.setContentType((MediaType.APPLICATION_XML));
HttpEntity entity = new HttpEntity(resource, headers);
ResponseEntity response = restTemplate.exchange(
URI, HttpMethod.PUT, entity, Foo.class, resource.getId());
Foo fooResponse = response.getBody();
Assert.assertEquals(resource.getId(), fooResponse.getId());
}
O que é interessante aqui é que podemos misturar os tipos de mídia -we’re sending XML data but we’re waiting for JSON data back from the server. Isso mostra o quão poderoso é realmente o mecanismo de conversão do Spring.
6. Conclusão
Neste tutorial, vimos como o Spring MVC nos permite especificar e personalizar totalmente os conversores de mensagem Http paraautomatically marshall/unmarshall Java Entities to and from XML or JSON. Essa é, obviamente, uma definição simplista, e há muito mais que o mecanismo de conversão de mensagens pode fazer - como podemos ver no último exemplo de teste.
Também vimos como aproveitar o mesmo mecanismo poderoso com o clienteRestTemplate - levando a uma forma totalmente segura de consumir a API.
Como sempre, o código apresentado neste artigo está disponívelover on Github.