diff --git a/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageReader.java b/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageReader.java index 0cc0ba72af8..2ff5643dce6 100644 --- a/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageReader.java +++ b/spring-web/src/main/java/org/springframework/http/codec/ServerSentEventHttpMessageReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -119,21 +119,21 @@ public class ServerSentEventHttpMessageReader implements HttpMessageReader { + return tokens.flatMap(tokenBuffer -> { try { Object value = reader.readValue(tokenBuffer.asParser(getObjectMapper())); if (!Hints.isLoggingSuppressed(hints)) { @@ -122,16 +122,16 @@ public abstract class AbstractJackson2Decoder extends Jackson2CodecSupport imple return Hints.getLogPrefix(hints) + "Decoded [" + formatted + "]"; }); } - return value; + return Mono.justOrEmpty(value); } catch (InvalidDefinitionException ex) { - throw new CodecException("Type definition error: " + ex.getType(), ex); + return Mono.error(new CodecException("Type definition error: " + ex.getType(), ex)); } catch (JsonProcessingException ex) { - throw new DecodingException("JSON decoding error: " + ex.getOriginalMessage(), ex); + return Mono.error(new DecodingException("JSON decoding error: " + ex.getOriginalMessage(), ex)); } catch (IOException ex) { - throw new DecodingException("I/O error while parsing input stream", ex); + return Mono.error(new DecodingException("I/O error while parsing input stream", ex)); } }); } diff --git a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java index 67539dddc31..cb8c586d34d 100644 --- a/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java +++ b/spring-web/src/main/java/org/springframework/web/client/RestTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -77,7 +77,8 @@ import org.springframework.web.util.UriTemplateHandler; * modern alternative to the {@code RestTemplate} with efficient support for * both sync and async, as well as streaming scenarios. The {@code RestTemplate} * will be deprecated in a future version and will not have major new features - * added going forward. + * added going forward. See the WebClient section of the Spring Framework reference + * documentation for more details and example code. * * @author Arjen Poutsma * @author Brian Clozel diff --git a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java index 2b8b62be565..f8dea0c25cc 100644 --- a/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java +++ b/spring-web/src/test/java/org/springframework/http/codec/json/Jackson2JsonDecoderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,6 +38,7 @@ import org.springframework.core.codec.AbstractDecoderTestCase; import org.springframework.core.codec.CodecException; import org.springframework.core.codec.DecodingException; import org.springframework.core.io.buffer.DataBuffer; +import org.springframework.http.MediaType; import org.springframework.http.codec.Pojo; import org.springframework.util.MimeType; @@ -174,6 +175,14 @@ public class Jackson2JsonDecoderTests extends AbstractDecoderTestCase result = this.decoder.decode(Flux.concat(stringBuffer("null")), + ResolvableType.forType(Pojo.class), MediaType.APPLICATION_JSON, Collections.emptyMap()); + + StepVerifier.create(result).expectComplete().verify(); + } + @Test public void noDefaultConstructor() { Flux input = diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java index 595d9acdf10..f17a4bf9668 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.core.Ordered; import org.springframework.lang.Nullable; +import org.springframework.util.StringUtils; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.ModelAndView; @@ -99,14 +100,16 @@ public abstract class AbstractHandlerExceptionResolver implements HandlerExcepti /** * Set the log category for warn logging. The name will be passed to the underlying logger * implementation through Commons Logging, getting interpreted as a log category according - * to the logger's configuration. - *

Default is no warn logging. Specify this setting to activate warn logging into a specific + * to the logger's configuration. If {@code null} is passed, warn logging is turned off. + *

By default there is no warn logging although sub-classes like + * {@link org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver} + * can change that default. Specify this setting to activate warn logging into a specific * category. Alternatively, override the {@link #logException} method for custom logging. * @see org.apache.commons.logging.LogFactory#getLog(String) * @see java.util.logging.Logger#getLogger(String) */ public void setWarnLogCategory(String loggerName) { - this.warnLogger = LogFactory.getLog(loggerName); + this.warnLogger = !StringUtils.isEmpty(loggerName) ? LogFactory.getLog(loggerName) : null; } /** diff --git a/src/docs/asciidoc/web/webflux-webclient.adoc b/src/docs/asciidoc/web/webflux-webclient.adoc index 06ecec01c80..d9b5e5aead9 100644 --- a/src/docs/asciidoc/web/webflux-webclient.adoc +++ b/src/docs/asciidoc/web/webflux-webclient.adoc @@ -541,6 +541,58 @@ WebClient client = webClient.mutate() +[[webflux-client-synchronous]] +== Synchronous Use + +`WebClient` can be used in synchronous style by blocking at the end for the result: + +[source,java,intent=0] +[subs="verbatim,quotes"] +---- +Person person = client.get().uri("/person/{id}", i).retrieve() + .bodyToMono(Person.class) + .block(); + +List persons = client.get().uri("/persons").retrieve() + .bodyToFlux(Person.class) + .collectList() + .block(); +---- + +However if multiple calls need to be made, it's more efficient to avoid blocking on each +response individually, and instead wait for the combined result: + +[source,java,intent=0] +[subs="verbatim,quotes"] +---- +Mono personMono = client.get().uri("/person/{id}", personId) + .retrieve().bodyToMono(Person.class); + +Mono> hobbiesMono = client.get().uri("/person/{id}/hobbies", personId) + .retrieve().bodyToFlux(Hobby.class).collectList(); + +Map data = Mono.zip(personMono, hobbiesMono, (person, hobbies) -> { + Map map = new LinkedHashMap<>(); + map.put("person", personName); + map.put("hobbies", hobbies); + return map; + }) + .block(); +---- + +The above is merely one example. There are lots of other patterns and operators for putting +together a reactive pipeline that makes many remote calls, potentially some nested, +inter-dependent, without ever blocking until the end. + +[NOTE] +==== +You should never have to block in a Spring MVC controller. Simply return the resulting +`Flux` or `Mono` from the controller method. +==== + + + + [[webflux-client-testing]] == Testing diff --git a/src/docs/asciidoc/web/webmvc-client.adoc b/src/docs/asciidoc/web/webmvc-client.adoc index 56c96d50599..69bfa6ba958 100644 --- a/src/docs/asciidoc/web/webmvc-client.adoc +++ b/src/docs/asciidoc/web/webmvc-client.adoc @@ -14,9 +14,11 @@ Spring REST client and exposes a simple, template-method API over underlying HTT libraries. NOTE: As of 5.0, the non-blocking, reactive `WebClient` offers a modern alternative to the -`RestTemplate`, with efficient support for both synchronous and asynchronous, as well as streaming -scenarios. The `RestTemplate` will be deprecated in a future version and will not have -major new features added going forward. +`RestTemplate`, with efficient support for both +<>, as well as +streaming scenarios. The `RestTemplate` will be deprecated in a future version and will +not have major new features added going forward. + See <> for details.