diff --git a/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeDataBinder.java b/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeDataBinder.java
index 75d0894dbb..8abcd8e286 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeDataBinder.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/support/WebExchangeDataBinder.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2023 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.
@@ -71,14 +71,30 @@ public class WebExchangeDataBinder extends WebDataBinder {
}
+ /**
+ * Use a default or single data constructor to create the target by
+ * binding request parameters, multipart files, or parts to constructor args.
+ *
After the call, use {@link #getBindingResult()} to check for bind errors.
+ * If there are none, the target is set, and {@link #bind} can be called for
+ * further initialization via setters.
+ * @param exchange the request to bind
+ * @return a {@code Mono} that completes when the target is created
+ * @since 6.1
+ */
+ public Mono construct(ServerWebExchange exchange) {
+ return getValuesToBind(exchange)
+ .doOnNext(map -> construct(new MapValueResolver(map)))
+ .then();
+ }
+
/**
* Bind query parameters, form data, or multipart form data to the binder target.
* @param exchange the current exchange
- * @return a {@code Mono} when binding is complete
+ * @return a {@code Mono} that completes when binding is complete
*/
public Mono bind(ServerWebExchange exchange) {
return getValuesToBind(exchange)
- .doOnNext(values -> doBind(new MutablePropertyValues(values)))
+ .doOnNext(map -> doBind(new MutablePropertyValues(map)))
.then();
}
@@ -128,4 +144,22 @@ public class WebExchangeDataBinder extends WebDataBinder {
}
}
-}
+
+ /**
+ * Resolve values from a map.
+ */
+ private static class MapValueResolver implements ValueResolver {
+
+ private final Map map;
+
+ private MapValueResolver(Map map) {
+ this.map = map;
+ }
+
+ @Override
+ public Object resolveValue(String name, Class> type) {
+ return this.map.get(name);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/BindingContext.java b/spring-webflux/src/main/java/org/springframework/web/reactive/BindingContext.java
index 96379aa2a8..f336047019 100644
--- a/spring-webflux/src/main/java/org/springframework/web/reactive/BindingContext.java
+++ b/spring-webflux/src/main/java/org/springframework/web/reactive/BindingContext.java
@@ -24,6 +24,7 @@ import reactor.core.publisher.Mono;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapterRegistry;
+import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.ui.Model;
import org.springframework.validation.DataBinder;
@@ -92,8 +93,7 @@ public class BindingContext {
/**
- * Create a {@link WebExchangeDataBinder} to apply data binding and
- * validation with on the target, command object.
+ * Create a binder with a target object.
* @param exchange the current exchange
* @param target the object to create a data binder for
* @param name the name of the target object
@@ -101,11 +101,44 @@ public class BindingContext {
* @throws ServerErrorException if {@code @InitBinder} method invocation fails
*/
public WebExchangeDataBinder createDataBinder(ServerWebExchange exchange, @Nullable Object target, String name) {
+ return createDataBinder(exchange, target, name, null);
+ }
+
+ /**
+ * Shortcut method to create a binder without a target object.
+ * @param exchange the current exchange
+ * @param name the name of the target object
+ * @return the created data binder
+ * @throws ServerErrorException if {@code @InitBinder} method invocation fails
+ */
+ public WebExchangeDataBinder createDataBinder(ServerWebExchange exchange, String name) {
+ return createDataBinder(exchange, null, name, null);
+ }
+
+ /**
+ * Create a binder with a target object and a {@code MethodParameter}.
+ * If the target is {@code null}, then
+ * {@link WebExchangeDataBinder#setTargetType targetType} is set.
+ * @since 6.1
+ */
+ public WebExchangeDataBinder createDataBinder(
+ ServerWebExchange exchange, @Nullable Object target, String name, @Nullable MethodParameter parameter) {
+
WebExchangeDataBinder dataBinder = new ExtendedWebExchangeDataBinder(target, name);
+ if (target == null && parameter != null) {
+ dataBinder.setTargetType(ResolvableType.forMethodParameter(parameter));
+ }
+
if (this.initializer != null) {
this.initializer.initBinder(dataBinder);
}
- return initDataBinder(dataBinder, exchange);
+ dataBinder = initDataBinder(dataBinder, exchange);
+
+ if (this.methodValidationApplicable && parameter != null) {
+ MethodValidationInitializer.initBinder(dataBinder, parameter);
+ }
+
+ return dataBinder;
}
/**
@@ -116,36 +149,6 @@ public class BindingContext {
return binder;
}
- /**
- * Create a {@link WebExchangeDataBinder} without a target object for type
- * conversion of request values to simple types.
- * @param exchange the current exchange
- * @param name the name of the target object
- * @return the created data binder
- * @throws ServerErrorException if {@code @InitBinder} method invocation fails
- */
- public WebExchangeDataBinder createDataBinder(ServerWebExchange exchange, String name) {
- return createDataBinder(exchange, null, name);
- }
-
- /**
- * Variant of {@link #createDataBinder(ServerWebExchange, Object, String)}
- * with a {@link MethodParameter} for which the {@code DataBinder} is created.
- * That may provide more insight to initialize the {@link WebExchangeDataBinder}.
- *
By default, if the parameter has {@code @Valid}, Bean Validation is
- * excluded, deferring to method validation.
- * @since 6.1
- */
- public WebExchangeDataBinder createDataBinder(
- ServerWebExchange exchange, @Nullable Object target, String name, MethodParameter parameter) {
-
- WebExchangeDataBinder dataBinder = createDataBinder(exchange, target, name);
- if (this.methodValidationApplicable) {
- MethodValidationInitializer.updateBinder(dataBinder, parameter);
- }
- return dataBinder;
- }
-
/**
* Extended variant of {@link WebExchangeDataBinder}, adding path variables.
@@ -170,7 +173,7 @@ public class BindingContext {
*/
private static class MethodValidationInitializer {
- public static void updateBinder(DataBinder binder, MethodParameter parameter) {
+ public static void initBinder(DataBinder binder, MethodParameter parameter) {
if (ReactiveAdapterRegistry.getSharedInstance().getAdapter(parameter.getParameterType()) == null) {
for (Annotation annotation : parameter.getParameterAnnotations()) {
if (annotation.annotationType().getName().equals("jakarta.validation.Valid")) {
diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java
index 32c005ddc6..d9987cc9f8 100644
--- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java
+++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolver.java
@@ -17,10 +17,7 @@
package org.springframework.web.reactive.result.method.annotation;
import java.lang.annotation.Annotation;
-import java.lang.reflect.Constructor;
-import java.util.List;
import java.util.Map;
-import java.util.Optional;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Sinks;
@@ -31,7 +28,6 @@ import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
-import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.ui.Model;
import org.springframework.util.Assert;
@@ -100,72 +96,74 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
public Mono