Allow to specify request body type in RestTemplate
This commit allows to specify the request body type in order to serialize generic types with a GenericHttpMessageConverter if needed. Issue: SPR-13154
This commit is contained in:
parent
7b1fcfc7c3
commit
3329abffc8
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2015 the original author or authors.
|
* Copyright 2002-2016 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package org.springframework.http;
|
package org.springframework.http;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
@ -54,6 +55,7 @@ import org.springframework.util.ObjectUtils;
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @author Arjen Poutsma
|
* @author Arjen Poutsma
|
||||||
|
* @author Sebastien Deleuze
|
||||||
* @since 4.1
|
* @since 4.1
|
||||||
* @see #getMethod()
|
* @see #getMethod()
|
||||||
* @see #getUrl()
|
* @see #getUrl()
|
||||||
|
|
@ -64,6 +66,8 @@ public class RequestEntity<T> extends HttpEntity<T> {
|
||||||
|
|
||||||
private final URI url;
|
private final URI url;
|
||||||
|
|
||||||
|
private final Type type;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor with method and URL but without body nor headers.
|
* Constructor with method and URL but without body nor headers.
|
||||||
|
|
@ -81,7 +85,19 @@ public class RequestEntity<T> extends HttpEntity<T> {
|
||||||
* @param url the URL
|
* @param url the URL
|
||||||
*/
|
*/
|
||||||
public RequestEntity(T body, HttpMethod method, URI url) {
|
public RequestEntity(T body, HttpMethod method, URI url) {
|
||||||
this(body, null, method, url);
|
this(body, null, method, url, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor with method, URL, body and type but without headers.
|
||||||
|
* @param body the body
|
||||||
|
* @param method the method
|
||||||
|
* @param url the URL
|
||||||
|
* @param type the type used for generic type resolution
|
||||||
|
* @since 4.3
|
||||||
|
*/
|
||||||
|
public RequestEntity(T body, HttpMethod method, URI url, Type type) {
|
||||||
|
this(body, null, method, url, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -91,7 +107,7 @@ public class RequestEntity<T> extends HttpEntity<T> {
|
||||||
* @param url the URL
|
* @param url the URL
|
||||||
*/
|
*/
|
||||||
public RequestEntity(MultiValueMap<String, String> headers, HttpMethod method, URI url) {
|
public RequestEntity(MultiValueMap<String, String> headers, HttpMethod method, URI url) {
|
||||||
this(null, headers, method, url);
|
this(null, headers, method, url, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -102,9 +118,23 @@ public class RequestEntity<T> extends HttpEntity<T> {
|
||||||
* @param url the URL
|
* @param url the URL
|
||||||
*/
|
*/
|
||||||
public RequestEntity(T body, MultiValueMap<String, String> headers, HttpMethod method, URI url) {
|
public RequestEntity(T body, MultiValueMap<String, String> headers, HttpMethod method, URI url) {
|
||||||
|
this(body, headers, method, url, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor with method, URL, headers, body and type.
|
||||||
|
* @param body the body
|
||||||
|
* @param headers the headers
|
||||||
|
* @param method the method
|
||||||
|
* @param url the URL
|
||||||
|
* @param type the type used for generic type resolution
|
||||||
|
* @since 4.3
|
||||||
|
*/
|
||||||
|
public RequestEntity(T body, MultiValueMap<String, String> headers, HttpMethod method, URI url, Type type) {
|
||||||
super(body, headers);
|
super(body, headers);
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.url = url;
|
this.url = url;
|
||||||
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -124,6 +154,14 @@ public class RequestEntity<T> extends HttpEntity<T> {
|
||||||
return this.url;
|
return this.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the type of the request's body.
|
||||||
|
* @return the request's body type
|
||||||
|
* @since 4.3
|
||||||
|
*/
|
||||||
|
public Type getType() {
|
||||||
|
return (this.type == null && this.getBody() != null ? this.getBody().getClass() : this.type );
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
|
|
@ -327,6 +365,16 @@ public class RequestEntity<T> extends HttpEntity<T> {
|
||||||
* @return the built request entity
|
* @return the built request entity
|
||||||
*/
|
*/
|
||||||
<T> RequestEntity<T> body(T body);
|
<T> RequestEntity<T> body(T body);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the body and type of the request entity and build the RequestEntity.
|
||||||
|
* @param <T> the type of the body
|
||||||
|
* @param body the body of the request entity
|
||||||
|
* @param type the type of the body, useful for generic type resolution
|
||||||
|
* @return the built request entity
|
||||||
|
* @since 4.3
|
||||||
|
*/
|
||||||
|
<T> RequestEntity<T> body(T body, Type type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -396,6 +444,11 @@ public class RequestEntity<T> extends HttpEntity<T> {
|
||||||
public <T> RequestEntity<T> body(T body) {
|
public <T> RequestEntity<T> body(T body) {
|
||||||
return new RequestEntity<T>(body, this.headers, this.method, this.url);
|
return new RequestEntity<T>(body, this.headers, this.method, this.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> RequestEntity<T> body(T body, Type type) {
|
||||||
|
return new RequestEntity<T>(body, this.headers, this.method, this.url, type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -782,11 +782,34 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Object requestBody = this.requestEntity.getBody();
|
Object requestBody = this.requestEntity.getBody();
|
||||||
Class<?> requestType = requestBody.getClass();
|
Class<?> requestBodyClass = requestBody.getClass();
|
||||||
|
Type requestBodyType = (this.requestEntity instanceof RequestEntity ?
|
||||||
|
((RequestEntity<?>)this.requestEntity).getType() : requestBodyClass);
|
||||||
HttpHeaders requestHeaders = this.requestEntity.getHeaders();
|
HttpHeaders requestHeaders = this.requestEntity.getHeaders();
|
||||||
MediaType requestContentType = requestHeaders.getContentType();
|
MediaType requestContentType = requestHeaders.getContentType();
|
||||||
for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
|
for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
|
||||||
if (messageConverter.canWrite(requestType, requestContentType)) {
|
if (messageConverter instanceof GenericHttpMessageConverter) {
|
||||||
|
GenericHttpMessageConverter<Object> genericMessageConverter = (GenericHttpMessageConverter<Object>) messageConverter;
|
||||||
|
if (genericMessageConverter.canWrite(requestBodyType, requestBodyClass, requestContentType)) {
|
||||||
|
if (!requestHeaders.isEmpty()) {
|
||||||
|
httpRequest.getHeaders().putAll(requestHeaders);
|
||||||
|
}
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
if (requestContentType != null) {
|
||||||
|
logger.debug("Writing [" + requestBody + "] as \"" + requestContentType +
|
||||||
|
"\" using [" + messageConverter + "]");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
logger.debug("Writing [" + requestBody + "] using [" + messageConverter + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
genericMessageConverter.write(
|
||||||
|
requestBody, requestBodyType, requestContentType, httpRequest);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (messageConverter.canWrite(requestBodyClass, requestContentType)) {
|
||||||
if (!requestHeaders.isEmpty()) {
|
if (!requestHeaders.isEmpty()) {
|
||||||
httpRequest.getHeaders().putAll(requestHeaders);
|
httpRequest.getHeaders().putAll(requestHeaders);
|
||||||
}
|
}
|
||||||
|
|
@ -806,7 +829,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
String message = "Could not write request: no suitable HttpMessageConverter found for request type [" +
|
String message = "Could not write request: no suitable HttpMessageConverter found for request type [" +
|
||||||
requestType.getName() + "]";
|
requestBodyClass.getName() + "]";
|
||||||
if (requestContentType != null) {
|
if (requestContentType != null) {
|
||||||
message += " and content type [" + requestContentType + "]";
|
message += " and content type [" + requestContentType + "]";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2015 the original author or authors.
|
* Copyright 2002-2016 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -19,11 +19,14 @@ package org.springframework.http;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
import org.springframework.web.util.UriTemplate;
|
import org.springframework.web.util.UriTemplate;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
@ -148,4 +151,14 @@ public class RequestEntityTests {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // SPR-13154
|
||||||
|
public void types() throws URISyntaxException {
|
||||||
|
URI url = new URI("http://example.com");
|
||||||
|
List<String> body = Arrays.asList("foo", "bar");
|
||||||
|
ParameterizedTypeReference<?> typeReference = new ParameterizedTypeReference<List<String>>() {};
|
||||||
|
|
||||||
|
RequestEntity<?> entity = RequestEntity.post(url).body(body, typeReference.getType());
|
||||||
|
assertEquals(typeReference.getType(), entity.getType());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2015 the original author or authors.
|
* Copyright 2002-2016 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -20,11 +20,16 @@ import java.io.UnsupportedEncodingException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.core.ParameterizedTypeReference;
|
||||||
import org.springframework.core.io.ClassPathResource;
|
import org.springframework.core.io.ClassPathResource;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpEntity;
|
||||||
|
|
@ -32,6 +37,7 @@ import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpMethod;
|
import org.springframework.http.HttpMethod;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.RequestEntity;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||||
import org.springframework.http.converter.json.MappingJacksonValue;
|
import org.springframework.http.converter.json.MappingJacksonValue;
|
||||||
|
|
@ -215,7 +221,7 @@ public class RestTemplateIntegrationTests extends AbstractJettyServerTestCase {
|
||||||
bean.setWith2("with");
|
bean.setWith2("with");
|
||||||
bean.setWithout("without");
|
bean.setWithout("without");
|
||||||
HttpEntity<MySampleBean> entity = new HttpEntity<MySampleBean>(bean, entityHeaders);
|
HttpEntity<MySampleBean> entity = new HttpEntity<MySampleBean>(bean, entityHeaders);
|
||||||
String s = template.postForObject(baseUrl + "/jsonpost", entity, String.class, "post");
|
String s = template.postForObject(baseUrl + "/jsonpost", entity, String.class);
|
||||||
assertTrue(s.contains("\"with1\":\"with\""));
|
assertTrue(s.contains("\"with1\":\"with\""));
|
||||||
assertTrue(s.contains("\"with2\":\"with\""));
|
assertTrue(s.contains("\"with2\":\"with\""));
|
||||||
assertTrue(s.contains("\"without\":\"without\""));
|
assertTrue(s.contains("\"without\":\"without\""));
|
||||||
|
|
@ -229,7 +235,7 @@ public class RestTemplateIntegrationTests extends AbstractJettyServerTestCase {
|
||||||
MappingJacksonValue jacksonValue = new MappingJacksonValue(bean);
|
MappingJacksonValue jacksonValue = new MappingJacksonValue(bean);
|
||||||
jacksonValue.setSerializationView(MyJacksonView1.class);
|
jacksonValue.setSerializationView(MyJacksonView1.class);
|
||||||
HttpEntity<MappingJacksonValue> entity = new HttpEntity<MappingJacksonValue>(jacksonValue, entityHeaders);
|
HttpEntity<MappingJacksonValue> entity = new HttpEntity<MappingJacksonValue>(jacksonValue, entityHeaders);
|
||||||
String s = template.postForObject(baseUrl + "/jsonpost", entity, String.class, "post");
|
String s = template.postForObject(baseUrl + "/jsonpost", entity, String.class);
|
||||||
assertTrue(s.contains("\"with1\":\"with\""));
|
assertTrue(s.contains("\"with1\":\"with\""));
|
||||||
assertFalse(s.contains("\"with2\":\"with\""));
|
assertFalse(s.contains("\"with2\":\"with\""));
|
||||||
assertFalse(s.contains("\"without\":\"without\""));
|
assertFalse(s.contains("\"without\":\"without\""));
|
||||||
|
|
@ -243,6 +249,21 @@ public class RestTemplateIntegrationTests extends AbstractJettyServerTestCase {
|
||||||
assertEquals("Invalid content", helloWorld, s);
|
assertEquals("Invalid content", helloWorld, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // SPR-13154
|
||||||
|
public void jsonPostForObjectWithJacksonTypeInfoList() throws URISyntaxException {
|
||||||
|
List<ParentClass> list = new ArrayList<>();
|
||||||
|
list.add(new Foo("foo"));
|
||||||
|
list.add(new Bar("bar"));
|
||||||
|
ParameterizedTypeReference<?> typeReference = new ParameterizedTypeReference<List<ParentClass>>() {};
|
||||||
|
RequestEntity<List<ParentClass>> entity = RequestEntity
|
||||||
|
.post(new URI(baseUrl + "/jsonpost"))
|
||||||
|
.contentType(new MediaType("application", "json", Charset.forName("UTF-8")))
|
||||||
|
.body(list, typeReference.getType());
|
||||||
|
String content = template.exchange(entity, String.class).getBody();
|
||||||
|
assertTrue(content.contains("\"type\":\"foo\""));
|
||||||
|
assertTrue(content.contains("\"type\":\"bar\""));
|
||||||
|
}
|
||||||
|
|
||||||
public interface MyJacksonView1 {};
|
public interface MyJacksonView1 {};
|
||||||
public interface MyJacksonView2 {};
|
public interface MyJacksonView2 {};
|
||||||
|
|
||||||
|
|
@ -290,4 +311,47 @@ public class RestTemplateIntegrationTests extends AbstractJettyServerTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
|
||||||
|
public static class ParentClass {
|
||||||
|
|
||||||
|
private String parentProperty;
|
||||||
|
|
||||||
|
public ParentClass() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParentClass(String parentProperty) {
|
||||||
|
this.parentProperty = parentProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getParentProperty() {
|
||||||
|
return parentProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParentProperty(String parentProperty) {
|
||||||
|
this.parentProperty = parentProperty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonTypeName("foo")
|
||||||
|
public static class Foo extends ParentClass {
|
||||||
|
|
||||||
|
public Foo() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Foo(String parentProperty) {
|
||||||
|
super(parentProperty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonTypeName("bar")
|
||||||
|
public static class Bar extends ParentClass {
|
||||||
|
|
||||||
|
public Bar() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bar(String parentProperty) {
|
||||||
|
super(parentProperty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -699,9 +699,9 @@ public class RestTemplateTests {
|
||||||
given(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.POST)).willReturn(this.request);
|
given(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.POST)).willReturn(this.request);
|
||||||
HttpHeaders requestHeaders = new HttpHeaders();
|
HttpHeaders requestHeaders = new HttpHeaders();
|
||||||
given(this.request.getHeaders()).willReturn(requestHeaders);
|
given(this.request.getHeaders()).willReturn(requestHeaders);
|
||||||
given(converter.canWrite(String.class, null)).willReturn(true);
|
given(converter.canWrite(String.class, String.class, null)).willReturn(true);
|
||||||
String requestBody = "Hello World";
|
String requestBody = "Hello World";
|
||||||
converter.write(requestBody, null, this.request);
|
converter.write(requestBody, String.class, null, this.request);
|
||||||
given(this.request.execute()).willReturn(response);
|
given(this.request.execute()).willReturn(response);
|
||||||
given(errorHandler.hasError(response)).willReturn(false);
|
given(errorHandler.hasError(response)).willReturn(false);
|
||||||
List<Integer> expected = Collections.singletonList(42);
|
List<Integer> expected = Collections.singletonList(42);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue