Add body(Object) method to ServerResponse.BodyBuilder

This method introduces a new body(Object) to ServerResponse, a shortcut
to body(BodyInserters.fromObject(Object)).

Note that in the implementation of the method, an `instanceof` check is
performed to make sure that the passed argument is not a `Publisher`,
as users should call `body(Publisher, Class)` for sending a reactive
type.

This Publisher-check is also done in the `WebClient`, for the same
reasons.

Issue: SPR-15461
This commit is contained in:
Arjen Poutsma 2017-04-19 16:40:23 +02:00
parent b897f96e0f
commit 30f61e0c07
7 changed files with 59 additions and 19 deletions

View File

@ -35,6 +35,7 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.reactive.ClientHttpRequest;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@ -258,19 +259,22 @@ class DefaultWebClient implements WebClient {
}
@Override
public <T> RequestHeadersSpec<?> body(BodyInserter<T, ? super ClientHttpRequest> inserter) {
public RequestHeadersSpec<?> body(BodyInserter<?, ? super ClientHttpRequest> inserter) {
this.inserter = inserter;
return this;
}
@Override
public <T, S extends Publisher<T>> RequestHeadersSpec<?> body(S publisher, Class<T> elementClass) {
public <T, P extends Publisher<T>> RequestHeadersSpec<?> body(P publisher, Class<T> elementClass) {
this.inserter = BodyInserters.fromPublisher(publisher, elementClass);
return this;
}
@Override
public <T> RequestHeadersSpec<?> body(T body) {
public RequestHeadersSpec<?> body(Object body) {
Assert.isTrue(!(body instanceof Publisher), "Please specify the element class by " +
"using body(Publisher, Class)");
this.inserter = BodyInserters.fromObject(body);
return this;
}

View File

@ -445,10 +445,9 @@ public interface WebClient {
/**
* Set the body of the request to the given {@code BodyInserter}.
* @param inserter the {@code BodyInserter} that writes to the request
* @param <T> the type contained in the body
* @return this builder
*/
<T> RequestHeadersSpec<?> body(BodyInserter<T, ? super ClientHttpRequest> inserter);
RequestHeadersSpec<?> body(BodyInserter<?, ? super ClientHttpRequest> inserter);
/**
* Set the body of the request to the given {@code Publisher}.
@ -458,10 +457,10 @@ public interface WebClient {
* @param publisher the {@code Publisher} to write to the request
* @param elementClass the class of elements contained in the publisher
* @param <T> the type of the elements contained in the publisher
* @param <S> the type of the {@code Publisher}
* @param <P> the type of the {@code Publisher}
* @return this builder
*/
<T, S extends Publisher<T>> RequestHeadersSpec<?> body(S publisher, Class<T> elementClass);
<T, P extends Publisher<T>> RequestHeadersSpec<?> body(P publisher, Class<T> elementClass);
/**
* Set the body of the request to the given {@code Object}.
@ -469,10 +468,9 @@ public interface WebClient {
* {@linkplain org.springframework.web.reactive.function.BodyInserters#fromObject
* Object body inserter}.
* @param body the {@code Object} to write to the request
* @param <T> the type contained in the body
* @return this builder
*/
<T> RequestHeadersSpec<?> body(T body);
RequestHeadersSpec<?> body(Object body);
}

View File

@ -189,7 +189,21 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
}
@Override
public <T> Mono<ServerResponse> body(BodyInserter<T, ? super ServerHttpResponse> inserter) {
public Mono<ServerResponse> body(Object body) {
Assert.notNull(body, "'body' must not be null");
Assert.isTrue(!(body instanceof Publisher), "Please specify the element class by using " +
"body(Publisher, Class)");
return new DefaultEntityResponseBuilder<>(body,
BodyInserters.fromObject(body))
.headers(this.headers)
.status(this.statusCode)
.build()
.map(entityResponse -> entityResponse);
}
@Override
public Mono<ServerResponse> body(BodyInserter<?, ? super ServerHttpResponse> inserter) {
Assert.notNull(inserter, "'inserter' must not be null");
return Mono.just(new BodyInserterServerResponse<>(this.statusCode, this.headers, inserter, this.hints));
}

View File

@ -341,17 +341,27 @@ public interface ServerResponse {
* @param elementClass the class of elements contained in the publisher
* @param <T> the type of the elements contained in the publisher
* @param <P> the type of the {@code Publisher}
* @return the built request
* @return the built response
*/
<T, P extends Publisher<T>> Mono<ServerResponse> body(P publisher, Class<T> elementClass);
/**
* Set the body of the response to the given {@code Object} and return it.
* This convenience method combines {@link #body(BodyInserter)} and
* {@link BodyInserters#fromObject(Object)}.
* @param body the body of the response
* @return the built response
* @throws IllegalArgumentException if {@code body} is a {@link Publisher}, for which
* {@link #body(Publisher, Class)} should be used.
*/
Mono<ServerResponse> body(Object body);
/**
* Set the body of the response to the given {@code BodyInserter} and return it.
* @param inserter the {@code BodyInserter} that writes to the response
* @param <T> the type contained in the body
* @return the built response
*/
<T> Mono<ServerResponse> body(BodyInserter<T, ? super ServerHttpResponse> inserter);
Mono<ServerResponse> body(BodyInserter<?, ? super ServerHttpResponse> inserter);
/**
* Render the template with the given {@code name} using the given {@code modelAttributes}.

View File

@ -116,6 +116,14 @@ public class DefaultWebClientTests {
verifyNoMoreInteractions(this.exchangeFunction);
}
@Test(expected = IllegalArgumentException.class)
public void bodyObjectPublisher() throws Exception {
Mono<Void> mono = Mono.empty();
WebClient client = builder().build();
client.post().uri("http://example.com").body(mono);
}
private WebClient.Builder builder() {
return WebClient.builder().baseUrl("/base").exchangeFunction(this.exchangeFunction);

View File

@ -35,9 +35,8 @@ import org.springframework.http.MediaType;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* @author Arjen Poutsma
@ -295,4 +294,12 @@ public class DefaultServerResponseBuilderTests {
StepVerifier.create(response.getBody()).expectComplete().verify();
}
@Test(expected = IllegalArgumentException.class)
public void bodyObjectPublisher() throws Exception {
Mono<Void> mono = Mono.empty();
ServerResponse.ok().body(mono);
}
}

View File

@ -25,7 +25,6 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import static org.junit.Assert.*;
import static org.springframework.web.reactive.function.BodyInserters.fromObject;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.path;
import static org.springframework.web.reactive.function.server.RouterFunctions.nest;
@ -82,11 +81,11 @@ public class NestedRouteIntegrationTests extends AbstractRouterFunctionIntegrati
private static class NestedHandler {
public Mono<ServerResponse> bar(ServerRequest request) {
return ServerResponse.ok().body(fromObject("bar"));
return ServerResponse.ok().body("bar");
}
public Mono<ServerResponse> baz(ServerRequest request) {
return ServerResponse.ok().body(fromObject("baz"));
return ServerResponse.ok().body("baz");
}
public Mono<ServerResponse> variables(ServerRequest request) {