Merge branch '5.1.x'
This commit is contained in:
commit
60e4649ff3
|
@ -19,16 +19,18 @@ package org.springframework.web.reactive.result.view;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.beans.factory.BeanNameAware;
|
import org.springframework.beans.factory.BeanNameAware;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.ApplicationContextAware;
|
import org.springframework.context.ApplicationContextAware;
|
||||||
|
@ -37,6 +39,8 @@ import org.springframework.core.ReactiveAdapterRegistry;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
|
import org.springframework.web.reactive.BindingContext;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,9 +58,6 @@ public abstract class AbstractView implements View, BeanNameAware, ApplicationCo
|
||||||
/** Logger that is available to subclasses. */
|
/** Logger that is available to subclasses. */
|
||||||
protected final Log logger = LogFactory.getLog(getClass());
|
protected final Log logger = LogFactory.getLog(getClass());
|
||||||
|
|
||||||
private static final Object NO_VALUE = new Object();
|
|
||||||
|
|
||||||
|
|
||||||
private final ReactiveAdapterRegistry adapterRegistry;
|
private final ReactiveAdapterRegistry adapterRegistry;
|
||||||
|
|
||||||
private final List<MediaType> mediaTypes = new ArrayList<>(4);
|
private final List<MediaType> mediaTypes = new ArrayList<>(4);
|
||||||
|
@ -211,30 +212,32 @@ public abstract class AbstractView implements View, BeanNameAware, ApplicationCo
|
||||||
* <p>The default implementation creates a combined output Map that includes
|
* <p>The default implementation creates a combined output Map that includes
|
||||||
* model as well as static attributes with the former taking precedence.
|
* model as well as static attributes with the former taking precedence.
|
||||||
*/
|
*/
|
||||||
protected Mono<Map<String, Object>> getModelAttributes(@Nullable Map<String, ?> model,
|
protected Mono<Map<String, Object>> getModelAttributes(
|
||||||
ServerWebExchange exchange) {
|
@Nullable Map<String, ?> model, ServerWebExchange exchange) {
|
||||||
|
|
||||||
int size = (model != null ? model.size() : 0);
|
int size = (model != null ? model.size() : 0);
|
||||||
|
Map<String, Object> attributes = new ConcurrentHashMap<>(size);
|
||||||
Map<String, Object> attributes = new LinkedHashMap<>(size);
|
|
||||||
if (model != null) {
|
if (model != null) {
|
||||||
attributes.putAll(model);
|
attributes.putAll(model);
|
||||||
}
|
}
|
||||||
|
//noinspection deprecation
|
||||||
return resolveAsyncAttributes(attributes).then(Mono.just(attributes));
|
return resolveAsyncAttributes(attributes)
|
||||||
|
.then(resolveAsyncAttributes(attributes, exchange))
|
||||||
|
.doOnTerminate(() -> exchange.getAttributes().remove(BINDING_CONTEXT_ATTRIBUTE))
|
||||||
|
.thenReturn(attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* By default, resolve async attributes supported by the
|
* Use the configured {@link ReactiveAdapterRegistry} to adapt asynchronous
|
||||||
* {@link ReactiveAdapterRegistry} to their blocking counterparts.
|
* attributes to {@code Mono<T>} or {@code Mono<List<T>>} and then wait to
|
||||||
* <p>View implementations capable of taking advantage of reactive types
|
* resolve them into actual values. When the returned {@code Mono<Void>}
|
||||||
* can override this method if needed.
|
* completes, the asynchronous attributes in the model would have been
|
||||||
* @return {@code Mono} for the completion of async attributes resolution
|
* replaced with their corresponding resolved values.
|
||||||
|
* @return result {@code Mono} that completes when the model is ready
|
||||||
|
* @since 5.1.8
|
||||||
*/
|
*/
|
||||||
protected Mono<Void> resolveAsyncAttributes(Map<String, Object> model) {
|
protected Mono<Void> resolveAsyncAttributes(Map<String, Object> model, ServerWebExchange exchange) {
|
||||||
List<String> names = new ArrayList<>();
|
List<Mono<?>> asyncAttributes = null;
|
||||||
List<Mono<?>> valueMonos = new ArrayList<>();
|
|
||||||
|
|
||||||
for (Map.Entry<String, ?> entry : model.entrySet()) {
|
for (Map.Entry<String, ?> entry : model.entrySet()) {
|
||||||
Object value = entry.getValue();
|
Object value = entry.getValue();
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
|
@ -242,37 +245,60 @@ public abstract class AbstractView implements View, BeanNameAware, ApplicationCo
|
||||||
}
|
}
|
||||||
ReactiveAdapter adapter = this.adapterRegistry.getAdapter(null, value);
|
ReactiveAdapter adapter = this.adapterRegistry.getAdapter(null, value);
|
||||||
if (adapter != null) {
|
if (adapter != null) {
|
||||||
names.add(entry.getKey());
|
if (asyncAttributes == null) {
|
||||||
|
asyncAttributes = new ArrayList<>();
|
||||||
|
}
|
||||||
|
String name = entry.getKey();
|
||||||
if (adapter.isMultiValue()) {
|
if (adapter.isMultiValue()) {
|
||||||
Flux<Object> fluxValue = Flux.from(adapter.toPublisher(value));
|
asyncAttributes.add(
|
||||||
valueMonos.add(fluxValue.collectList().defaultIfEmpty(Collections.emptyList()));
|
Flux.from(adapter.toPublisher(value))
|
||||||
|
.collectList()
|
||||||
|
.doOnSuccess(result -> model.put(name, result)));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Mono<Object> monoValue = Mono.from(adapter.toPublisher(value));
|
asyncAttributes.add(
|
||||||
valueMonos.add(monoValue.defaultIfEmpty(NO_VALUE));
|
Mono.from(adapter.toPublisher(value))
|
||||||
|
.doOnSuccess(result -> {
|
||||||
|
if (result != null) {
|
||||||
|
model.put(name, result);
|
||||||
|
addBindingResult(name, result, model, exchange);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
model.remove(name);
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return asyncAttributes != null ? Mono.when(asyncAttributes) : Mono.empty();
|
||||||
|
}
|
||||||
|
|
||||||
if (names.isEmpty()) {
|
private void addBindingResult(String name, Object value, Map<String, Object> model, ServerWebExchange exchange) {
|
||||||
|
BindingContext context = exchange.getAttribute(BINDING_CONTEXT_ATTRIBUTE);
|
||||||
|
if (context == null || value.getClass().isArray() || value instanceof Collection ||
|
||||||
|
value instanceof Map || BeanUtils.isSimpleValueType(value.getClass())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BindingResult result = context.createDataBinder(exchange, value, name).getBindingResult();
|
||||||
|
model.put(BindingResult.MODEL_KEY_PREFIX + name, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the configured {@link ReactiveAdapterRegistry} to adapt asynchronous
|
||||||
|
* attributes to {@code Mono<T>} or {@code Mono<List<T>>} and then wait to
|
||||||
|
* resolve them into actual values. When the returned {@code Mono<Void>}
|
||||||
|
* completes, the asynchronous attributes in the model would have been
|
||||||
|
* replaced with their corresponding resolved values.
|
||||||
|
* @return result {@code Mono} that completes when the model is ready
|
||||||
|
* @deprecated as of 5.1.8 this method is still invoked but it is a no-op.
|
||||||
|
* Please, use {@link #resolveAsyncAttributes(Map, ServerWebExchange)}
|
||||||
|
* instead. It is invoked after this one and does the actual work.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
protected Mono<Void> resolveAsyncAttributes(Map<String, Object> model) {
|
||||||
return Mono.empty();
|
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a RequestContext to expose under the specified attribute name.
|
* Create a RequestContext to expose under the specified attribute name.
|
||||||
* <p>The default implementation creates a standard RequestContext instance
|
* <p>The default implementation creates a standard RequestContext instance
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -44,6 +44,18 @@ import org.springframework.web.server.ServerWebExchange;
|
||||||
*/
|
*/
|
||||||
public interface View {
|
public interface View {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The name of the exchange attribute that contains the
|
||||||
|
* {@link org.springframework.web.reactive.BindingContext BindingContext}
|
||||||
|
* for the request which can be used to create
|
||||||
|
* {@link org.springframework.validation.BindingResult BindingResult}
|
||||||
|
* instances for objects in to the model.
|
||||||
|
* <p>Note: This attribute is not required and may not be present.
|
||||||
|
* @since 5.1.8
|
||||||
|
*/
|
||||||
|
String BINDING_CONTEXT_ATTRIBUTE = View.class.getName() + ".bindingContext";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the list of media types this View supports, or an empty list.
|
* Return the list of media types this View supports, or an empty list.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -245,10 +245,9 @@ public class ViewResolutionResultHandler extends HandlerResultHandlerSupport
|
||||||
model.addAttribute(name, returnValue);
|
model.addAttribute(name, returnValue);
|
||||||
viewsMono = resolveViews(getDefaultViewName(exchange), locale);
|
viewsMono = resolveViews(getDefaultViewName(exchange), locale);
|
||||||
}
|
}
|
||||||
|
BindingContext bindingContext = result.getBindingContext();
|
||||||
updateBindingContext(result.getBindingContext(), exchange);
|
updateBindingResult(bindingContext, exchange);
|
||||||
|
return viewsMono.flatMap(views -> render(views, model.asMap(), bindingContext, exchange));
|
||||||
return viewsMono.flatMap(views -> render(views, model.asMap(), exchange));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,39 +287,42 @@ public class ViewResolutionResultHandler extends HandlerResultHandlerSupport
|
||||||
.orElseGet(() -> Conventions.getVariableNameForParameter(returnType));
|
.orElseGet(() -> Conventions.getVariableNameForParameter(returnType));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateBindingContext(BindingContext context, ServerWebExchange exchange) {
|
private void updateBindingResult(BindingContext context, ServerWebExchange exchange) {
|
||||||
Map<String, Object> model = context.getModel().asMap();
|
Map<String, Object> model = context.getModel().asMap();
|
||||||
model.keySet().stream()
|
for (Map.Entry<String, Object> entry : model.entrySet()) {
|
||||||
.filter(name -> isBindingCandidate(name, model.get(name)))
|
String name = entry.getKey();
|
||||||
.filter(name -> !model.containsKey(BindingResult.MODEL_KEY_PREFIX + name))
|
Object value = entry.getValue();
|
||||||
.forEach(name -> {
|
if (isBindingCandidate(name, value)) {
|
||||||
WebExchangeDataBinder binder = context.createDataBinder(exchange, model.get(name), name);
|
if (!model.containsKey(BindingResult.MODEL_KEY_PREFIX + name)) {
|
||||||
|
WebExchangeDataBinder binder = context.createDataBinder(exchange, value, name);
|
||||||
model.put(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
|
model.put(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isBindingCandidate(String name, @Nullable Object value) {
|
private boolean isBindingCandidate(String name, @Nullable Object value) {
|
||||||
return (!name.startsWith(BindingResult.MODEL_KEY_PREFIX) && value != null &&
|
return (!name.startsWith(BindingResult.MODEL_KEY_PREFIX) && value != null &&
|
||||||
!value.getClass().isArray() && !(value instanceof Collection) &&
|
!value.getClass().isArray() && !(value instanceof Collection) && !(value instanceof Map) &&
|
||||||
!(value instanceof Map) && !BeanUtils.isSimpleValueType(value.getClass()));
|
getAdapterRegistry().getAdapter(null, value) == null &&
|
||||||
|
!BeanUtils.isSimpleValueType(value.getClass()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Mono<? extends Void> render(List<View> views, Map<String, Object> model,
|
private Mono<? extends Void> render(List<View> views, Map<String, Object> model,
|
||||||
ServerWebExchange exchange) {
|
BindingContext bindingContext, ServerWebExchange exchange) {
|
||||||
|
|
||||||
for (View view : views) {
|
for (View view : views) {
|
||||||
if (view.isRedirectView()) {
|
if (view.isRedirectView()) {
|
||||||
return view.render(model, null, exchange);
|
return renderWith(view, model, null, exchange, bindingContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<MediaType> mediaTypes = getMediaTypes(views);
|
List<MediaType> mediaTypes = getMediaTypes(views);
|
||||||
MediaType bestMediaType = selectMediaType(exchange, () -> mediaTypes);
|
MediaType bestMediaType = selectMediaType(exchange, () -> mediaTypes);
|
||||||
if (bestMediaType != null) {
|
if (bestMediaType != null) {
|
||||||
for (View view : views) {
|
for (View view : views) {
|
||||||
for (MediaType mediaType : view.getSupportedMediaTypes()) {
|
for (MediaType mediaType : view.getSupportedMediaTypes()) {
|
||||||
if (mediaType.isCompatibleWith(bestMediaType)) {
|
if (mediaType.isCompatibleWith(bestMediaType)) {
|
||||||
return view.render(model, mediaType, exchange);
|
return renderWith(view, model, mediaType, exchange, bindingContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -328,6 +330,14 @@ public class ViewResolutionResultHandler extends HandlerResultHandlerSupport
|
||||||
throw new NotAcceptableStatusException(mediaTypes);
|
throw new NotAcceptableStatusException(mediaTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Mono<? extends Void> renderWith(View view, Map<String, Object> model,
|
||||||
|
@Nullable MediaType mediaType, ServerWebExchange exchange, BindingContext bindingContext) {
|
||||||
|
|
||||||
|
exchange.getAttributes().put(View.BINDING_CONTEXT_ATTRIBUTE, bindingContext);
|
||||||
|
return view.render(model, mediaType, exchange)
|
||||||
|
.doOnTerminate(() -> exchange.getAttributes().remove(View.BINDING_CONTEXT_ATTRIBUTE));
|
||||||
|
}
|
||||||
|
|
||||||
private List<MediaType> getMediaTypes(List<View> views) {
|
private List<MediaType> getMediaTypes(List<View> views) {
|
||||||
return views.stream()
|
return views.stream()
|
||||||
.flatMap(view -> view.getSupportedMediaTypes().stream())
|
.flatMap(view -> view.getSupportedMediaTypes().stream())
|
||||||
|
|
|
@ -16,8 +16,9 @@
|
||||||
|
|
||||||
package org.springframework.web.reactive.result.view;
|
package org.springframework.web.reactive.result.view;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
|
@ -28,14 +29,15 @@ import reactor.core.publisher.Mono;
|
||||||
import reactor.test.StepVerifier;
|
import reactor.test.StepVerifier;
|
||||||
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||||
import org.springframework.mock.web.test.server.MockServerWebExchange;
|
import org.springframework.mock.web.test.server.MockServerWebExchange;
|
||||||
import org.springframework.tests.sample.beans.TestBean;
|
import org.springframework.tests.sample.beans.TestBean;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
|
import org.springframework.web.reactive.BindingContext;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.*;
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertNull;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for {@link AbstractView}.
|
* Unit tests for {@link AbstractView}.
|
||||||
|
@ -53,25 +55,31 @@ public class AbstractViewTests {
|
||||||
|
|
||||||
TestBean testBean1 = new TestBean("Bean1");
|
TestBean testBean1 = new TestBean("Bean1");
|
||||||
TestBean testBean2 = new TestBean("Bean2");
|
TestBean testBean2 = new TestBean("Bean2");
|
||||||
Map<String, Object> attributes = new HashMap<>();
|
|
||||||
attributes.put("attr1", Mono.just(testBean1));
|
Map<String, Object> inMap = new HashMap<>();
|
||||||
attributes.put("attr2", Flux.just(testBean1, testBean2));
|
inMap.put("attr1", Mono.just(testBean1).delayElement(Duration.ofMillis(10)));
|
||||||
attributes.put("attr3", Single.just(testBean2));
|
inMap.put("attr2", Flux.just(testBean1, testBean2).delayElements(Duration.ofMillis(10)));
|
||||||
attributes.put("attr4", Observable.just(testBean1, testBean2));
|
inMap.put("attr3", Single.just(testBean2));
|
||||||
attributes.put("attr5", Mono.empty());
|
inMap.put("attr4", Observable.just(testBean1, testBean2));
|
||||||
|
inMap.put("attr5", Mono.empty());
|
||||||
|
|
||||||
|
this.exchange.getAttributes().put(View.BINDING_CONTEXT_ATTRIBUTE, new BindingContext());
|
||||||
|
|
||||||
TestView view = new TestView();
|
TestView view = new TestView();
|
||||||
StepVerifier.create(
|
StepVerifier.create(view.render(inMap, null, this.exchange)).verifyComplete();
|
||||||
view.render(attributes, null, this.exchange)).verifyComplete();
|
|
||||||
|
|
||||||
Map<String, Object> actual = view.attributes;
|
Map<String, Object> outMap = view.attributes;
|
||||||
assertEquals(testBean1, actual.get("attr1"));
|
assertEquals(testBean1, outMap.get("attr1"));
|
||||||
assertArrayEquals(new TestBean[] { testBean1, testBean2 },
|
assertEquals(Arrays.asList(testBean1, testBean2), outMap.get("attr2"));
|
||||||
((List<TestBean>) actual.get("attr2")).toArray());
|
assertEquals(testBean2, outMap.get("attr3"));
|
||||||
assertEquals(testBean2, actual.get("attr3"));
|
assertEquals(Arrays.asList(testBean1, testBean2), outMap.get("attr4"));
|
||||||
assertArrayEquals(new TestBean[] { testBean1, testBean2 },
|
assertNull(outMap.get("attr5"));
|
||||||
((List<TestBean>) actual.get("attr4")).toArray());
|
|
||||||
assertNull(actual.get("attr5"));
|
assertNotNull(outMap.get(BindingResult.MODEL_KEY_PREFIX + "attr1"));
|
||||||
|
assertNotNull(outMap.get(BindingResult.MODEL_KEY_PREFIX + "attr3"));
|
||||||
|
assertNull(outMap.get(BindingResult.MODEL_KEY_PREFIX + "attr2"));
|
||||||
|
assertNull(outMap.get(BindingResult.MODEL_KEY_PREFIX + "attr4"));
|
||||||
|
assertNull(outMap.get(BindingResult.MODEL_KEY_PREFIX + "attr5"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class TestView extends AbstractView {
|
private static class TestView extends AbstractView {
|
||||||
|
@ -80,7 +88,7 @@ public class AbstractViewTests {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Mono<Void> renderInternal(Map<String, Object> renderAttributes,
|
protected Mono<Void> renderInternal(Map<String, Object> renderAttributes,
|
||||||
MediaType contentType, ServerWebExchange exchange) {
|
@Nullable MediaType contentType, ServerWebExchange exchange) {
|
||||||
|
|
||||||
this.attributes = renderAttributes;
|
this.attributes = renderAttributes;
|
||||||
return Mono.empty();
|
return Mono.empty();
|
||||||
|
|
Loading…
Reference in New Issue