diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/AbstractView.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/AbstractView.java
index 9a32fb19ca..5335adb725 100644
--- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/AbstractView.java
+++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/view/AbstractView.java
@@ -20,9 +20,9 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -211,30 +211,27 @@ public abstract class AbstractView implements View, BeanNameAware, ApplicationCo
*
The default implementation creates a combined output Map that includes
* model as well as static attributes with the former taking precedence.
*/
- protected Mono> getModelAttributes(@Nullable Map model,
- ServerWebExchange exchange) {
+ protected Mono> getModelAttributes(
+ @Nullable Map model, ServerWebExchange exchange) {
int size = (model != null ? model.size() : 0);
-
- Map attributes = new LinkedHashMap<>(size);
+ Map attributes = new ConcurrentHashMap<>(size);
if (model != null) {
attributes.putAll(model);
}
-
- return resolveAsyncAttributes(attributes).then(Mono.just(attributes));
+ return resolveAsyncAttributes(attributes).thenReturn(attributes);
}
/**
- * By default, resolve async attributes supported by the
- * {@link ReactiveAdapterRegistry} to their blocking counterparts.
- * View implementations capable of taking advantage of reactive types
- * can override this method if needed.
- * @return {@code Mono} for the completion of async attributes resolution
+ * Use the configured {@link ReactiveAdapterRegistry} to adapt asynchronous
+ * model attributes to {@code Mono} or {@code Mono>} and resolve
+ * them to actual values via {@link Mono#zip(Mono, Mono)}, so that when
+ * the returned result {@code Mono} completes, the model has its asynchronous
+ * attributes replaced with synchronous values.
+ * @return result {@code Mono} that completes when the model is ready
*/
protected Mono resolveAsyncAttributes(Map model) {
- List names = new ArrayList<>();
- List> valueMonos = new ArrayList<>();
-
+ List> asyncAttributes = null;
for (Map.Entry entry : model.entrySet()) {
Object value = entry.getValue();
if (value == null) {
@@ -242,35 +239,34 @@ public abstract class AbstractView implements View, BeanNameAware, ApplicationCo
}
ReactiveAdapter adapter = this.adapterRegistry.getAdapter(null, value);
if (adapter != null) {
- names.add(entry.getKey());
+ if (asyncAttributes == null) {
+ asyncAttributes = new ArrayList<>();
+ }
+ String name = entry.getKey();
if (adapter.isMultiValue()) {
- Flux fluxValue = Flux.from(adapter.toPublisher(value));
- valueMonos.add(fluxValue.collectList().defaultIfEmpty(Collections.emptyList()));
+ asyncAttributes.add(
+ Flux.from(adapter.toPublisher(value))
+ .collectList()
+ .doOnSuccess(result -> {
+ result = result != null ? result : Collections.emptyList();
+ model.put(name, result);
+ }));
}
else {
- Mono monoValue = Mono.from(adapter.toPublisher(value));
- valueMonos.add(monoValue.defaultIfEmpty(NO_VALUE));
+ asyncAttributes.add(
+ Mono.from(adapter.toPublisher(value))
+ .doOnSuccess(result -> {
+ if (result != null) {
+ model.put(name, result);
+ }
+ else {
+ model.remove(name);
+ }
+ }));
}
}
}
-
- if (names.isEmpty()) {
- return Mono.empty();
- }
-
- return Mono.zip(valueMonos,
- values -> {
- for (int i=0; i < values.length; i++) {
- if (values[i] != NO_VALUE) {
- model.put(names.get(i), values[i]);
- }
- else {
- model.remove(names.get(i));
- }
- }
- return NO_VALUE;
- })
- .then();
+ return asyncAttributes != null ? Mono.when(asyncAttributes) : Mono.empty();
}
/**
diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/AbstractViewTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/AbstractViewTests.java
index 2f6519091a..0a9c239a7c 100644
--- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/AbstractViewTests.java
+++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/view/AbstractViewTests.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.
@@ -16,6 +16,7 @@
package org.springframework.web.reactive.result.view;
+import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -54,8 +55,8 @@ public class AbstractViewTests {
TestBean testBean1 = new TestBean("Bean1");
TestBean testBean2 = new TestBean("Bean2");
Map attributes = new HashMap<>();
- attributes.put("attr1", Mono.just(testBean1));
- attributes.put("attr2", Flux.just(testBean1, testBean2));
+ attributes.put("attr1", Mono.just(testBean1).delayElement(Duration.ofMillis(10)));
+ attributes.put("attr2", Flux.just(testBean1, testBean2).delayElements(Duration.ofMillis(10)));
attributes.put("attr3", Single.just(testBean2));
attributes.put("attr4", Observable.just(testBean1, testBean2));
attributes.put("attr5", Mono.empty());