Use ResolvableType to create WebDataBinder
This provides more flexibility to pass a targetType even if a MethodParameter is not available. See gh-26721
This commit is contained in:
@@ -81,19 +81,19 @@ public class DefaultDataBinderFactory implements WebDataBinderFactory {
|
||||
@Override
|
||||
public final WebDataBinder createBinder(
|
||||
NativeWebRequest webRequest, @Nullable Object target, String objectName,
|
||||
MethodParameter parameter) throws Exception {
|
||||
ResolvableType type) throws Exception {
|
||||
|
||||
return createBinderInternal(webRequest, target, objectName, parameter);
|
||||
return createBinderInternal(webRequest, target, objectName, type);
|
||||
}
|
||||
|
||||
private WebDataBinder createBinderInternal(
|
||||
NativeWebRequest webRequest, @Nullable Object target, String objectName,
|
||||
@Nullable MethodParameter parameter) throws Exception {
|
||||
@Nullable ResolvableType type) throws Exception {
|
||||
|
||||
WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
|
||||
|
||||
if (target == null && parameter != null) {
|
||||
dataBinder.setTargetType(ResolvableType.forMethodParameter(parameter));
|
||||
if (target == null && type != null) {
|
||||
dataBinder.setTargetType(type);
|
||||
}
|
||||
|
||||
if (this.initializer != null) {
|
||||
@@ -101,8 +101,10 @@ public class DefaultDataBinderFactory implements WebDataBinderFactory {
|
||||
}
|
||||
initBinder(dataBinder, webRequest);
|
||||
|
||||
if (this.methodValidationApplicable && parameter != null) {
|
||||
MethodValidationInitializer.initBinder(dataBinder, parameter);
|
||||
if (this.methodValidationApplicable && type != null) {
|
||||
if (type.getSource() instanceof MethodParameter parameter) {
|
||||
MethodValidationInitializer.initBinder(dataBinder, parameter);
|
||||
}
|
||||
}
|
||||
|
||||
return dataBinder;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
package org.springframework.web.bind.support;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
@@ -44,13 +44,14 @@ public interface WebDataBinderFactory {
|
||||
|
||||
/**
|
||||
* Variant of {@link #createBinder(NativeWebRequest, Object, String)} with a
|
||||
* {@link MethodParameter} for which the {@code DataBinder} is created. This
|
||||
* may provide more insight to initialize the {@link WebDataBinder}.
|
||||
* {@link ResolvableType} for which the {@code DataBinder} is created.
|
||||
* This may be used to construct the target, or otherwise provide more
|
||||
* insight on how to initialize the binder.
|
||||
* @since 6.1
|
||||
*/
|
||||
default WebDataBinder createBinder(
|
||||
NativeWebRequest webRequest, @Nullable Object target, String objectName,
|
||||
MethodParameter parameter) throws Exception {
|
||||
ResolvableType targetType) throws Exception {
|
||||
|
||||
return createBinder(webRequest, target, objectName);
|
||||
}
|
||||
|
||||
@@ -148,13 +148,7 @@ public class WebExchangeDataBinder extends WebDataBinder {
|
||||
/**
|
||||
* Resolve values from a map.
|
||||
*/
|
||||
private static class MapValueResolver implements ValueResolver {
|
||||
|
||||
private final Map<String, Object> map;
|
||||
|
||||
private MapValueResolver(Map<String, Object> map) {
|
||||
this.map = map;
|
||||
}
|
||||
private record MapValueResolver(Map<String, Object> map) implements ValueResolver {
|
||||
|
||||
@Override
|
||||
public Object resolveValue(String name, Class<?> type) {
|
||||
@@ -162,4 +156,4 @@ public class WebExchangeDataBinder extends WebDataBinder {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
@@ -142,7 +143,8 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
|
||||
|
||||
// No BindingResult yet, proceed with binding and validation
|
||||
if (bindingResult == null) {
|
||||
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name, parameter);
|
||||
ResolvableType type = ResolvableType.forMethodParameter(parameter);
|
||||
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name, type);
|
||||
if (attribute == null) {
|
||||
constructAttribute(binder, webRequest);
|
||||
attribute = wrapAsOptionalIfNecessary(parameter, binder.getTarget());
|
||||
|
||||
@@ -179,7 +179,8 @@ public class ModelAttributeMethodProcessorTests {
|
||||
|
||||
StubRequestDataBinder dataBinder = new StubRequestDataBinder(target, name);
|
||||
WebDataBinderFactory factory = mock();
|
||||
given(factory.createBinder(this.request, target, name, this.paramNamedValidModelAttr)).willReturn(dataBinder);
|
||||
ResolvableType type = ResolvableType.forMethodParameter(this.paramNamedValidModelAttr);
|
||||
given(factory.createBinder(this.request, target, name, type)).willReturn(dataBinder);
|
||||
|
||||
this.processor.resolveArgument(this.paramNamedValidModelAttr, this.container, this.request, factory);
|
||||
|
||||
@@ -198,7 +199,8 @@ public class ModelAttributeMethodProcessorTests {
|
||||
|
||||
StubRequestDataBinder dataBinder = new StubRequestDataBinder(target, name);
|
||||
WebDataBinderFactory factory = mock();
|
||||
given(factory.createBinder(this.request, target, name, this.paramNamedValidModelAttr)).willReturn(dataBinder);
|
||||
ResolvableType type = ResolvableType.forMethodParameter(this.paramNamedValidModelAttr);
|
||||
given(factory.createBinder(this.request, target, name, type)).willReturn(dataBinder);
|
||||
|
||||
this.processor.resolveArgument(this.paramNamedValidModelAttr, this.container, this.request, factory);
|
||||
|
||||
@@ -214,7 +216,8 @@ public class ModelAttributeMethodProcessorTests {
|
||||
|
||||
StubRequestDataBinder dataBinder = new StubRequestDataBinder(target, name);
|
||||
WebDataBinderFactory factory = mock();
|
||||
given(factory.createBinder(this.request, target, name, this.paramBindingDisabledAttr)).willReturn(dataBinder);
|
||||
ResolvableType type = ResolvableType.forMethodParameter(this.paramBindingDisabledAttr);
|
||||
given(factory.createBinder(this.request, target, name, type)).willReturn(dataBinder);
|
||||
|
||||
this.processor.resolveArgument(this.paramBindingDisabledAttr, this.container, this.request, factory);
|
||||
|
||||
@@ -232,12 +235,13 @@ public class ModelAttributeMethodProcessorTests {
|
||||
dataBinder.getBindingResult().reject("error");
|
||||
|
||||
WebDataBinderFactory binderFactory = mock();
|
||||
given(binderFactory.createBinder(this.request, target, name, this.paramNonSimpleType)).willReturn(dataBinder);
|
||||
ResolvableType type = ResolvableType.forMethodParameter(this.paramNonSimpleType);
|
||||
given(binderFactory.createBinder(this.request, target, name, type)).willReturn(dataBinder);
|
||||
|
||||
assertThatExceptionOfType(MethodArgumentNotValidException.class).isThrownBy(() ->
|
||||
this.processor.resolveArgument(this.paramNonSimpleType, this.container, this.request, binderFactory));
|
||||
|
||||
verify(binderFactory).createBinder(this.request, target, name, this.paramNonSimpleType);
|
||||
verify(binderFactory).createBinder(this.request, target, name, type);
|
||||
}
|
||||
|
||||
@Test // SPR-9378
|
||||
@@ -252,7 +256,8 @@ public class ModelAttributeMethodProcessorTests {
|
||||
|
||||
StubRequestDataBinder dataBinder = new StubRequestDataBinder(testBean, name);
|
||||
WebDataBinderFactory binderFactory = mock();
|
||||
given(binderFactory.createBinder(this.request, testBean, name, this.paramModelAttr)).willReturn(dataBinder);
|
||||
ResolvableType type = ResolvableType.forMethodParameter(this.paramModelAttr);
|
||||
given(binderFactory.createBinder(this.request, testBean, name, type)).willReturn(dataBinder);
|
||||
|
||||
this.processor.resolveArgument(this.paramModelAttr, this.container, this.request, binderFactory);
|
||||
|
||||
@@ -301,10 +306,11 @@ public class ModelAttributeMethodProcessorTests {
|
||||
|
||||
WebDataBinder dataBinder = new WebRequestDataBinder(target);
|
||||
WebDataBinderFactory factory = mock();
|
||||
given(factory.createBinder(this.request, target, expectedAttrName, param)).willReturn(dataBinder);
|
||||
ResolvableType type = ResolvableType.forMethodParameter(param);
|
||||
given(factory.createBinder(this.request, target, expectedAttrName, type)).willReturn(dataBinder);
|
||||
|
||||
this.processor.resolveArgument(param, this.container, this.request, factory);
|
||||
verify(factory).createBinder(this.request, target, expectedAttrName, param);
|
||||
verify(factory).createBinder(this.request, target, expectedAttrName, type);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -116,17 +116,17 @@ public class BindingContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a binder with a target object and a {@code MethodParameter}.
|
||||
* Create a binder with a target object and a {@link ResolvableType targetType}.
|
||||
* 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) {
|
||||
ServerWebExchange exchange, @Nullable Object target, String name, @Nullable ResolvableType targetType) {
|
||||
|
||||
WebExchangeDataBinder dataBinder = new ExtendedWebExchangeDataBinder(target, name);
|
||||
if (target == null && parameter != null) {
|
||||
dataBinder.setTargetType(ResolvableType.forMethodParameter(parameter));
|
||||
if (target == null && targetType != null) {
|
||||
dataBinder.setTargetType(targetType);
|
||||
}
|
||||
|
||||
if (this.initializer != null) {
|
||||
@@ -134,8 +134,10 @@ public class BindingContext {
|
||||
}
|
||||
dataBinder = initDataBinder(dataBinder, exchange);
|
||||
|
||||
if (this.methodValidationApplicable && parameter != null) {
|
||||
MethodValidationInitializer.initBinder(dataBinder, parameter);
|
||||
if (this.methodValidationApplicable && targetType != null) {
|
||||
if (targetType.getSource() instanceof MethodParameter parameter) {
|
||||
MethodValidationInitializer.initBinder(dataBinder, parameter);
|
||||
}
|
||||
}
|
||||
|
||||
return dataBinder;
|
||||
|
||||
@@ -265,11 +265,12 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho
|
||||
return null;
|
||||
}
|
||||
|
||||
private void validate(Object target, Object[] validationHints, MethodParameter param,
|
||||
private void validate(Object target, Object[] validationHints, MethodParameter parameter,
|
||||
BindingContext binding, ServerWebExchange exchange) {
|
||||
|
||||
String name = Conventions.getVariableNameForParameter(param);
|
||||
WebExchangeDataBinder binder = binding.createDataBinder(exchange, target, name, param);
|
||||
String name = Conventions.getVariableNameForParameter(parameter);
|
||||
ResolvableType type = ResolvableType.forMethodParameter(parameter);
|
||||
WebExchangeDataBinder binder = binding.createDataBinder(exchange, target, name, type);
|
||||
try {
|
||||
LocaleContextHolder.setLocaleContext(exchange.getLocaleContext());
|
||||
binder.validate(validationHints);
|
||||
@@ -278,7 +279,7 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho
|
||||
LocaleContextHolder.resetLocaleContext();
|
||||
}
|
||||
if (binder.getBindingResult().hasErrors()) {
|
||||
throw new WebExchangeBindException(param, binder.getBindingResult());
|
||||
throw new WebExchangeBindException(parameter, binder.getBindingResult());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ 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;
|
||||
@@ -150,14 +151,15 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR
|
||||
if (value == null) {
|
||||
value = removeReactiveAttribute(name, context.getModel());
|
||||
}
|
||||
ResolvableType type = ResolvableType.forMethodParameter(parameter);
|
||||
if (value != null) {
|
||||
ReactiveAdapter adapter = getAdapterRegistry().getAdapter(null, value);
|
||||
Assert.isTrue(adapter == null || !adapter.isMultiValue(), "Multi-value publisher is not supported");
|
||||
return (adapter != null ? Mono.from(adapter.toPublisher(value)) : Mono.just(value))
|
||||
.map(attr -> context.createDataBinder(exchange, attr, name, parameter));
|
||||
.map(attr -> context.createDataBinder(exchange, attr, name, type));
|
||||
}
|
||||
else {
|
||||
WebExchangeDataBinder binder = context.createDataBinder(exchange, null, name, parameter);
|
||||
WebExchangeDataBinder binder = context.createDataBinder(exchange, null, name, type);
|
||||
return constructAttribute(binder, exchange).thenReturn(binder);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.List;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -139,7 +140,8 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
|
||||
HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, name);
|
||||
arg = readWithMessageConverters(inputMessage, parameter, parameter.getNestedGenericParameterType());
|
||||
if (binderFactory != null) {
|
||||
WebDataBinder binder = binderFactory.createBinder(request, arg, name, parameter);
|
||||
ResolvableType type = ResolvableType.forMethodParameter(parameter);
|
||||
WebDataBinder binder = binderFactory.createBinder(request, arg, name, type);
|
||||
if (arg != null) {
|
||||
validateIfApplicable(binder, parameter);
|
||||
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.List;
|
||||
|
||||
import org.springframework.core.Conventions;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.ProblemDetail;
|
||||
@@ -134,7 +135,8 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
|
||||
String name = Conventions.getVariableNameForParameter(parameter);
|
||||
|
||||
if (binderFactory != null) {
|
||||
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name, parameter);
|
||||
ResolvableType type = ResolvableType.forMethodParameter(parameter);
|
||||
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name, type);
|
||||
if (arg != null) {
|
||||
validateIfApplicable(binder, parameter);
|
||||
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
|
||||
|
||||
Reference in New Issue
Block a user