Consistent support for path variable and multipart binding

Closes gh-24107
Closes gh-22169
Closes gh-25265
This commit is contained in:
Juergen Hoeller
2020-08-06 18:28:09 +02:00
parent 5bdbbdfcfb
commit d61c0ee57d
13 changed files with 365 additions and 85 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2020 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,8 +16,16 @@
package org.springframework.web.reactive;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
import reactor.core.publisher.Mono;
import org.springframework.http.codec.multipart.Part;
import org.springframework.lang.Nullable;
import org.springframework.ui.Model;
import org.springframework.util.MultiValueMap;
import org.springframework.validation.support.BindingAwareConcurrentModel;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.bind.support.WebExchangeDataBinder;
@@ -35,6 +43,7 @@ import org.springframework.web.server.ServerWebExchange;
* <p>Container for the default model for the request.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 5.0
*/
public class BindingContext {
@@ -79,7 +88,7 @@ public class BindingContext {
* @throws ServerErrorException if {@code @InitBinder} method invocation fails
*/
public WebExchangeDataBinder createDataBinder(ServerWebExchange exchange, @Nullable Object target, String name) {
WebExchangeDataBinder dataBinder = new WebExchangeDataBinder(target, name);
WebExchangeDataBinder dataBinder = new ExtendedWebExchangeDataBinder(target, name);
if (this.initializer != null) {
this.initializer.initBinder(dataBinder);
}
@@ -106,4 +115,34 @@ public class BindingContext {
return createDataBinder(exchange, null, name);
}
/**
* Extended variant of {@link WebExchangeDataBinder}, adding path variables.
*/
private static class ExtendedWebExchangeDataBinder extends WebExchangeDataBinder {
public ExtendedWebExchangeDataBinder(@Nullable Object target, String objectName) {
super(target, objectName);
}
@Override
public Mono<Map<String, Object>> getValuesToBind(ServerWebExchange exchange) {
Map<String, String> vars = exchange.getAttributeOrDefault(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, Collections.emptyMap());
MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
Mono<MultiValueMap<String, String>> formData = exchange.getFormData();
Mono<MultiValueMap<String, Part>> multipartData = exchange.getMultipartData();
return Mono.zip(Mono.just(vars), Mono.just(queryParams), formData, multipartData)
.map(tuple -> {
Map<String, Object> result = new TreeMap<>();
tuple.getT1().forEach(result::put);
tuple.getT2().forEach((key, values) -> addBindValue(result, key, values));
tuple.getT3().forEach((key, values) -> addBindValue(result, key, values));
tuple.getT4().forEach((key, values) -> addBindValue(result, key, values));
return result;
});
}
}
}

View File

@@ -41,7 +41,6 @@ import org.springframework.util.ClassUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.support.WebExchangeBindException;
import org.springframework.web.bind.support.WebExchangeDataBinder;
@@ -233,7 +232,8 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
}
// A single data class constructor -> resolve constructor arguments from request parameters.
return WebExchangeDataBinder.extractValuesToBind(exchange).map(bindValues -> {
WebExchangeDataBinder binder = context.createDataBinder(exchange, null, attributeName);
return getValuesToBind(binder, exchange).map(bindValues -> {
ConstructorProperties cp = ctor.getAnnotation(ConstructorProperties.class);
String[] paramNames = (cp != null ? cp.value() : parameterNameDiscoverer.getParameterNames(ctor));
Assert.state(paramNames != null, () -> "Cannot resolve parameter names for constructor " + ctor);
@@ -241,7 +241,6 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
Assert.state(paramNames.length == paramTypes.length,
() -> "Invalid number of parameter names: " + paramNames.length + " for constructor " + ctor);
Object[] args = new Object[paramTypes.length];
WebDataBinder binder = context.createDataBinder(exchange, null, attributeName);
String fieldDefaultPrefix = binder.getFieldDefaultPrefix();
String fieldMarkerPrefix = binder.getFieldMarkerPrefix();
for (int i = 0; i < paramNames.length; i++) {
@@ -271,6 +270,18 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
});
}
/**
* Protected method to obtain the values for data binding. By default this
* method delegates to {@link WebExchangeDataBinder#getValuesToBind}.
* @param binder the data binder in use
* @param exchange the current exchange
* @return a map of bind values
* @since 5.3
*/
public Mono<Map<String, Object>> getValuesToBind(WebExchangeDataBinder binder, ServerWebExchange exchange) {
return binder.getValuesToBind(exchange);
}
private boolean hasErrorsArgument(MethodParameter parameter) {
int i = parameter.getParameterIndex();
Class<?>[] paramTypes = parameter.getExecutable().getParameterTypes();