From 11a416156b71b11977d0792c90fe3b46358c050b Mon Sep 17 00:00:00 2001 From: rstoyanchev Date: Mon, 26 Jun 2023 13:11:05 +0100 Subject: [PATCH] Use ResolvableType to create WebDataBinder This provides more flexibility to pass a targetType even if a MethodParameter is not available. See gh-26721 --- .../support/DefaultDataBinderFactory.java | 16 ++++++++------ .../bind/support/WebDataBinderFactory.java | 9 ++++---- .../bind/support/WebExchangeDataBinder.java | 10 ++------- .../ModelAttributeMethodProcessor.java | 4 +++- .../ModelAttributeMethodProcessorTests.java | 22 ++++++++++++------- .../web/reactive/BindingContext.java | 14 +++++++----- ...AbstractMessageReaderArgumentResolver.java | 9 ++++---- .../ModelAttributeMethodArgumentResolver.java | 6 +++-- .../RequestPartMethodArgumentResolver.java | 4 +++- .../RequestResponseBodyMethodProcessor.java | 4 +++- 10 files changed, 56 insertions(+), 42 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/bind/support/DefaultDataBinderFactory.java b/spring-web/src/main/java/org/springframework/web/bind/support/DefaultDataBinderFactory.java index f65b875fbb9c..372b714af593 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/support/DefaultDataBinderFactory.java +++ b/spring-web/src/main/java/org/springframework/web/bind/support/DefaultDataBinderFactory.java @@ -81,19 +81,19 @@ public final WebDataBinder createBinder( @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 @@ private WebDataBinder createBinderInternal( } 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; diff --git a/spring-web/src/main/java/org/springframework/web/bind/support/WebDataBinderFactory.java b/spring-web/src/main/java/org/springframework/web/bind/support/WebDataBinderFactory.java index bf1981a99bad..3411d363e239 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/support/WebDataBinderFactory.java +++ b/spring-web/src/main/java/org/springframework/web/bind/support/WebDataBinderFactory.java @@ -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 @@ WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, /** * 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); } 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 8abcd8e28623..6367227dfa02 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 @@ -148,13 +148,7 @@ protected static void addBindValue(Map params, String key, List< /** * Resolve values from a map. */ - private static class MapValueResolver implements ValueResolver { - - private final Map map; - - private MapValueResolver(Map map) { - this.map = map; - } + private record MapValueResolver(Map map) implements ValueResolver { @Override public Object resolveValue(String name, Class type) { @@ -162,4 +156,4 @@ public Object resolveValue(String name, Class type) { } } -} \ No newline at end of file +} diff --git a/spring-web/src/main/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessor.java b/spring-web/src/main/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessor.java index 929fc6b0faef..8faec818d2f3 100644 --- a/spring-web/src/main/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessor.java +++ b/spring-web/src/main/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessor.java @@ -26,6 +26,7 @@ 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 final Object resolveArgument(MethodParameter parameter, @Nullable ModelAn // 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()); diff --git a/spring-web/src/test/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessorTests.java b/spring-web/src/test/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessorTests.java index d926dd25d292..826d768463e2 100644 --- a/spring-web/src/test/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessorTests.java +++ b/spring-web/src/test/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessorTests.java @@ -179,7 +179,8 @@ public void resolveArgumentValidation() throws Exception { 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 void resolveArgumentBindingDisabledPreviously() throws Exception { 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 void resolveArgumentBindingDisabled() throws Exception { 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 void resolveArgumentBindException() throws Exception { 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 void resolveArgumentOrdering() throws Exception { 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 @@ private void testGetAttributeFromModel(String expectedAttrName, MethodParameter 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); } 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 f33604701969..803bb7d90555 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 @@ -116,17 +116,17 @@ public WebExchangeDataBinder createDataBinder(ServerWebExchange exchange, String } /** - * 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 WebExchangeDataBinder createDataBinder( } 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; diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java index cbb8db1ef277..db735c3e872b 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractMessageReaderArgumentResolver.java @@ -265,11 +265,12 @@ private Object[] extractValidationHints(MethodParameter parameter) { 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 @@ private void validate(Object target, Object[] validationHints, MethodParameter p LocaleContextHolder.resetLocaleContext(); } if (binder.getBindingResult().hasErrors()) { - throw new WebExchangeBindException(param, binder.getBindingResult()); + throw new WebExchangeBindException(parameter, binder.getBindingResult()); } } 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 d9987cc9f8b2..a68f7fbae93e 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 @@ -28,6 +28,7 @@ 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 @@ private Mono initDataBinder( 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); } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolver.java index 0059343764f0..47e21fcab2f3 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestPartMethodArgumentResolver.java @@ -23,6 +23,7 @@ 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 Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewC 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)) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java index 503d667f51eb..8419bd21c017 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java @@ -23,6 +23,7 @@ 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 Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewC 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)) {