Skip to content

Commit 172afb5

Browse files
committed
Polish AbstractView in WebFlux
1 parent ffd7cff commit 172afb5

File tree

2 files changed

+38
-41
lines changed

2 files changed

+38
-41
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/result/view/AbstractView.java

Lines changed: 34 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
import java.nio.charset.StandardCharsets;
2121
import java.util.ArrayList;
2222
import java.util.Collections;
23-
import java.util.LinkedHashMap;
2423
import java.util.List;
2524
import java.util.Map;
25+
import java.util.concurrent.ConcurrentHashMap;
2626

2727
import org.apache.commons.logging.Log;
2828
import org.apache.commons.logging.LogFactory;
@@ -211,66 +211,62 @@ public Mono<Void> render(@Nullable Map<String, ?> model, @Nullable MediaType con
211211
* <p>The default implementation creates a combined output Map that includes
212212
* model as well as static attributes with the former taking precedence.
213213
*/
214-
protected Mono<Map<String, Object>> getModelAttributes(@Nullable Map<String, ?> model,
215-
ServerWebExchange exchange) {
214+
protected Mono<Map<String, Object>> getModelAttributes(
215+
@Nullable Map<String, ?> model, ServerWebExchange exchange) {
216216

217217
int size = (model != null ? model.size() : 0);
218-
219-
Map<String, Object> attributes = new LinkedHashMap<>(size);
218+
Map<String, Object> attributes = new ConcurrentHashMap<>(size);
220219
if (model != null) {
221220
attributes.putAll(model);
222221
}
223-
224-
return resolveAsyncAttributes(attributes).then(Mono.just(attributes));
222+
return resolveAsyncAttributes(attributes).thenReturn(attributes);
225223
}
226224

227225
/**
228-
* By default, resolve async attributes supported by the
229-
* {@link ReactiveAdapterRegistry} to their blocking counterparts.
230-
* <p>View implementations capable of taking advantage of reactive types
231-
* can override this method if needed.
232-
* @return {@code Mono} for the completion of async attributes resolution
226+
* Use the configured {@link ReactiveAdapterRegistry} to adapt asynchronous
227+
* model attributes to {@code Mono<T>} or {@code Mono<List<T>>} and resolve
228+
* them to actual values via {@link Mono#zip(Mono, Mono)}, so that when
229+
* the returned result {@code Mono} completes, the model has its asynchronous
230+
* attributes replaced with synchronous values.
231+
* @return result {@code Mono} that completes when the model is ready
233232
*/
234233
protected Mono<Void> resolveAsyncAttributes(Map<String, Object> model) {
235-
List<String> names = new ArrayList<>();
236-
List<Mono<?>> valueMonos = new ArrayList<>();
237-
234+
List<Mono<?>> asyncAttributes = null;
238235
for (Map.Entry<String, ?> entry : model.entrySet()) {
239236
Object value = entry.getValue();
240237
if (value == null) {
241238
continue;
242239
}
243240
ReactiveAdapter adapter = this.adapterRegistry.getAdapter(null, value);
244241
if (adapter != null) {
245-
names.add(entry.getKey());
242+
if (asyncAttributes == null) {
243+
asyncAttributes = new ArrayList<>();
244+
}
245+
String name = entry.getKey();
246246
if (adapter.isMultiValue()) {
247-
Flux<Object> fluxValue = Flux.from(adapter.toPublisher(value));
248-
valueMonos.add(fluxValue.collectList().defaultIfEmpty(Collections.emptyList()));
247+
asyncAttributes.add(
248+
Flux.from(adapter.toPublisher(value))
249+
.collectList()
250+
.doOnSuccess(result -> {
251+
result = result != null ? result : Collections.emptyList();
252+
model.put(name, result);
253+
}));
249254
}
250255
else {
251-
Mono<Object> monoValue = Mono.from(adapter.toPublisher(value));
252-
valueMonos.add(monoValue.defaultIfEmpty(NO_VALUE));
256+
asyncAttributes.add(
257+
Mono.from(adapter.toPublisher(value))
258+
.doOnSuccess(result -> {
259+
if (result != null) {
260+
model.put(name, result);
261+
}
262+
else {
263+
model.remove(name);
264+
}
265+
}));
253266
}
254267
}
255268
}
256-
257-
if (names.isEmpty()) {
258-
return Mono.empty();
259-
}
260-
261-
return Mono.zip(valueMonos,
262-
values -> {
263-
for (int i=0; i < values.length; i++) {
264-
if (values[i] != NO_VALUE) {
265-
model.put(names.get(i), values[i]);
266-
}
267-
else {
268-
model.remove(names.get(i));
269-
}
270-
}
271-
return NO_VALUE;
272-
})
273-
.then();
269+
return asyncAttributes != null ? Mono.when(asyncAttributes) : Mono.empty();
274270
}
275271

276272
/**

spring-webflux/src/test/java/org/springframework/web/reactive/result/view/AbstractViewTests.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.web.reactive.result.view;
1818

19+
import java.time.Duration;
1920
import java.util.HashMap;
2021
import java.util.List;
2122
import java.util.Map;
@@ -54,8 +55,8 @@ public void resolveAsyncAttributes() {
5455
TestBean testBean1 = new TestBean("Bean1");
5556
TestBean testBean2 = new TestBean("Bean2");
5657
Map<String, Object> attributes = new HashMap<>();
57-
attributes.put("attr1", Mono.just(testBean1));
58-
attributes.put("attr2", Flux.just(testBean1, testBean2));
58+
attributes.put("attr1", Mono.just(testBean1).delayElement(Duration.ofMillis(10)));
59+
attributes.put("attr2", Flux.just(testBean1, testBean2).delayElements(Duration.ofMillis(10)));
5960
attributes.put("attr3", Single.just(testBean2));
6061
attributes.put("attr4", Observable.just(testBean1, testBean2));
6162
attributes.put("attr5", Mono.empty());

0 commit comments

Comments
 (0)