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());