Support constructing target object in DataBinder

See gh-26721
This commit is contained in:
rstoyanchev
2023-06-22 20:36:28 +01:00
parent 40bf923d7d
commit ea398d7b7e
11 changed files with 553 additions and 282 deletions

View File

@@ -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.
@@ -16,15 +16,21 @@
package org.springframework.web.bind;
import java.lang.reflect.Array;
import java.util.List;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.Part;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartRequest;
import org.springframework.web.multipart.support.StandardServletPartUtils;
import org.springframework.web.util.WebUtils;
@@ -46,10 +52,9 @@ import org.springframework.web.util.WebUtils;
* which include specifying allowed/required fields, and registering custom
* property editors.
*
* <p>Can also be used for manual data binding in custom web controllers:
* for example, in a plain Controller implementation or in a MultiActionController
* handler method. Simply instantiate a ServletRequestDataBinder for each binding
* process, and invoke {@code bind} with the current ServletRequest as argument:
* <p>Can also be used for manual data binding. Simply instantiate a
* ServletRequestDataBinder for each binding process, and invoke {@code bind}
* with the current ServletRequest as argument:
*
* <pre class="code">
* MyBean myBean = new MyBean();
@@ -94,6 +99,27 @@ public class ServletRequestDataBinder extends WebDataBinder {
}
/**
* Use a default or single data constructor to create the target by
* binding request parameters, multipart files, or parts to constructor args.
* <p>After the call, use {@link #getBindingResult()} to check for bind errors.
* If there are none, the target is set, and {@link #bind(ServletRequest)}
* can be called for further initialization via setters.
* @param request the request to bind
* @since 6.1
*/
public void construct(ServletRequest request) {
construct(createValueResolver(request));
}
/**
* Allow subclasses to create the {@link ValueResolver} instance to use.
* @since 6.1
*/
protected ServletRequestValueResolver createValueResolver(ServletRequest request) {
return new ServletRequestValueResolver(request, this);
}
/**
* Bind the parameters of the given request to this binder's target,
* also binding multipart files in case of a multipart request.
@@ -119,7 +145,7 @@ public class ServletRequestDataBinder extends WebDataBinder {
if (multipartRequest != null) {
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
}
else if (StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.MULTIPART_FORM_DATA_VALUE)) {
else if (isFormDataPost(request)) {
HttpServletRequest httpServletRequest = WebUtils.getNativeRequest(request, HttpServletRequest.class);
if (httpServletRequest != null && HttpMethod.POST.matches(httpServletRequest.getMethod())) {
StandardServletPartUtils.bindParts(httpServletRequest, mpvs, isBindEmptyMultipartFiles());
@@ -129,6 +155,10 @@ public class ServletRequestDataBinder extends WebDataBinder {
doBind(mpvs);
}
private static boolean isFormDataPost(ServletRequest request) {
return StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.MULTIPART_FORM_DATA_VALUE);
}
/**
* Extension point that subclasses can use to add extra bind values for a
* request. Invoked before {@link #doBind(MutablePropertyValues)}.
@@ -153,4 +183,73 @@ public class ServletRequestDataBinder extends WebDataBinder {
}
}
/**
* Return a {@code ServletRequest} {@link ValueResolver}. Mainly for use from
* {@link org.springframework.web.bind.support.WebRequestDataBinder}.
* @since 6.1
*/
public static ValueResolver valueResolver(ServletRequest request, WebDataBinder binder) {
return new ServletRequestValueResolver(request, binder);
}
/**
* Resolver that looks up values to bind in a {@link ServletRequest}.
*/
protected static class ServletRequestValueResolver implements ValueResolver {
private final ServletRequest request;
private final WebDataBinder dataBinder;
protected ServletRequestValueResolver(ServletRequest request, WebDataBinder dataBinder) {
this.request = request;
this.dataBinder = dataBinder;
}
protected ServletRequest getRequest() {
return this.request;
}
@Nullable
@Override
public final Object resolveValue(String name, Class<?> paramType) {
Object value = getRequestParameter(name, paramType);
if (value == null) {
value = this.dataBinder.resolvePrefixValue(name, paramType, this::getRequestParameter);
}
if (value == null) {
value = getMultipartValue(name);
}
return value;
}
@Nullable
protected Object getRequestParameter(String name, Class<?> type) {
Object value = this.request.getParameterValues(name);
return (ObjectUtils.isArray(value) && Array.getLength(value) == 1 ? Array.get(value, 0) : value);
}
@Nullable
private Object getMultipartValue(String name) {
MultipartRequest multipartRequest = WebUtils.getNativeRequest(this.request, MultipartRequest.class);
if (multipartRequest != null) {
List<MultipartFile> files = multipartRequest.getFiles(name);
if (!files.isEmpty()) {
return (files.size() == 1 ? files.get(0) : files);
}
}
else if (isFormDataPost(this.request)) {
HttpServletRequest httpRequest = WebUtils.getNativeRequest(this.request, HttpServletRequest.class);
if (httpRequest != null && HttpMethod.POST.matches(httpRequest.getMethod())) {
List<Part> parts = StandardServletPartUtils.getParts(httpRequest, name);
if (!parts.isEmpty()) {
return (parts.size() == 1 ? parts.get(0) : parts);
}
}
}
return null;
}
}
}

View File

@@ -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.
@@ -20,6 +20,7 @@ import java.lang.reflect.Array;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
@@ -193,6 +194,33 @@ public class WebDataBinder extends DataBinder {
}
/**
* Check if a value can be resolved if {@link #getFieldDefaultPrefix()}
* or {@link #getFieldMarkerPrefix()} is prepended.
* @param name the name of the value to resolve
* @param type the type of value expected
* @param resolver delegate resolver to use for the checks
* @return the resolved value, or {@code null}
* @since 6.1
*/
@Nullable
protected Object resolvePrefixValue(String name, Class<?> type, BiFunction<String, Class<?>, Object> resolver) {
Object value = resolver.apply(name, type);
if (value == null) {
String prefix = getFieldDefaultPrefix();
if (prefix != null) {
value = resolver.apply(prefix + name, type);
}
if (value == null) {
prefix = getFieldMarkerPrefix();
if (prefix != null && resolver.apply(prefix + name, type) != null) {
value = getEmptyValue(type);
}
}
}
return value;
}
/**
* This implementation performs a field default and marker check
* before delegating to the superclass binding process.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 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.
@@ -203,9 +203,11 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer
if (this.bindingErrorProcessor != null) {
binder.setBindingErrorProcessor(this.bindingErrorProcessor);
}
if (this.validator != null && binder.getTarget() != null &&
this.validator.supports(binder.getTarget().getClass())) {
binder.setValidator(this.validator);
if (this.validator != null) {
Class<?> type = getTargetType(binder);
if (type != null && this.validator.supports(type)) {
binder.setValidator(this.validator);
}
}
if (this.conversionService != null) {
binder.setConversionService(this.conversionService);
@@ -217,4 +219,16 @@ public class ConfigurableWebBindingInitializer implements WebBindingInitializer
}
}
@Nullable
private static Class<?> getTargetType(WebDataBinder binder) {
Class<?> type = null;
if (binder.getTarget() != null) {
type = binder.getTarget().getClass();
}
else if (binder.getTargetType() != null) {
type = binder.getTargetType().resolve();
}
return type;
}
}

View File

@@ -19,6 +19,7 @@ package org.springframework.web.bind.support;
import java.lang.annotation.Annotation;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
import org.springframework.validation.DataBinder;
import org.springframework.web.bind.WebDataBinder;
@@ -69,11 +70,41 @@ public class DefaultDataBinderFactory implements WebDataBinderFactory {
public final WebDataBinder createBinder(
NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception {
return createBinderInternal(webRequest, target, objectName, null);
}
/**
* {@inheritDoc}.
* <p>By default, if the parameter has {@code @Valid}, Bean Validation is
* excluded, deferring to method validation.
*/
@Override
public final WebDataBinder createBinder(
NativeWebRequest webRequest, @Nullable Object target, String objectName,
MethodParameter parameter) throws Exception {
return createBinderInternal(webRequest, target, objectName, parameter);
}
private WebDataBinder createBinderInternal(
NativeWebRequest webRequest, @Nullable Object target, String objectName,
@Nullable MethodParameter parameter) throws Exception {
WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
if (target == null && parameter != null) {
dataBinder.setTargetType(ResolvableType.forMethodParameter(parameter));
}
if (this.initializer != null) {
this.initializer.initBinder(dataBinder);
}
initBinder(dataBinder, webRequest);
if (this.methodValidationApplicable && parameter != null) {
MethodValidationInitializer.initBinder(dataBinder, parameter);
}
return dataBinder;
}
@@ -104,30 +135,13 @@ public class DefaultDataBinderFactory implements WebDataBinderFactory {
}
/**
* {@inheritDoc}.
* <p>By default, if the parameter has {@code @Valid}, Bean Validation is
* excluded, deferring to method validation.
*/
@Override
public WebDataBinder createBinder(
NativeWebRequest webRequest, @Nullable Object target, String objectName,
MethodParameter parameter) throws Exception {
WebDataBinder dataBinder = createBinder(webRequest, target, objectName);
if (this.methodValidationApplicable) {
MethodValidationInitializer.updateBinder(dataBinder, parameter);
}
return dataBinder;
}
/**
* Excludes Bean Validation if the method parameter has {@code @Valid}.
*/
private static class MethodValidationInitializer {
public static void updateBinder(DataBinder binder, MethodParameter parameter) {
public static void initBinder(DataBinder binder, MethodParameter parameter) {
for (Annotation annotation : parameter.getParameterAnnotations()) {
if (annotation.annotationType().getName().equals("jakarta.validation.Valid")) {
binder.setExcludedValidators(validator -> validator instanceof jakarta.validation.Validator);

View File

@@ -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.
@@ -16,6 +16,7 @@
package org.springframework.web.bind.support;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.MutablePropertyValues;
@@ -25,6 +26,7 @@ import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.WebRequest;
@@ -98,6 +100,25 @@ public class WebRequestDataBinder extends WebDataBinder {
}
/**
* Use a default or single data constructor to create the target by
* binding request parameters, multipart files, or parts to constructor args.
* <p>After the call, use {@link #getBindingResult()} to check for bind errors.
* If there are none, the target is set, and {@link #bind(WebRequest)}
* can be called for further initialization via setters.
* @param request the request to bind
* @since 6.1
*/
public void construct(WebRequest request) {
if (request instanceof NativeWebRequest nativeRequest) {
ServletRequest servletRequest = nativeRequest.getNativeRequest(ServletRequest.class);
if (servletRequest != null) {
construct(ServletRequestDataBinder.valueResolver(servletRequest, this));
}
}
}
/**
* Bind the parameters of the given request to this binder's target,
* also binding multipart files in case of a multipart request.

View File

@@ -17,37 +17,22 @@
package org.springframework.web.method.annotation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.Part;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.TypeMismatchException;
import org.springframework.core.KotlinDetector;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.ObjectError;
import org.springframework.validation.SmartValidator;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.ValidationAnnotationUtils;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
@@ -58,9 +43,6 @@ import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartRequest;
import org.springframework.web.multipart.support.StandardServletPartUtils;
/**
* Resolve {@code @ModelAttribute} annotated method arguments and handle
@@ -134,45 +116,45 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
mavContainer.setBinding(name, ann.binding());
}
Object attribute = null;
Object attribute;
BindingResult bindingResult = null;
if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
}
if (attribute == null || ObjectUtils.unwrapOptional(attribute) == null) {
bindingResult = binderFactory.createBinder(webRequest, null, name).getBindingResult();
attribute = wrapAsOptionalIfNecessary(parameter, null);
}
}
else {
// Create attribute instance
try {
// Mainly to allow subclasses alternative to create attribute
attribute = createAttribute(name, parameter, binderFactory, webRequest);
}
catch (MethodArgumentNotValidException ex) {
if (isBindExceptionRequired(parameter)) {
// No BindingResult parameter -> fail with BindException
throw ex;
}
// Otherwise, expose null/empty value and associated BindingResult
if (parameter.getParameterType() == Optional.class) {
attribute = Optional.empty();
}
else {
attribute = ex.getTarget();
}
attribute = wrapAsOptionalIfNecessary(parameter, ex.getTarget());
bindingResult = ex.getBindingResult();
}
}
// No BindingResult yet, proceed with binding and validation
if (bindingResult == null) {
// Bean property binding and validation;
// skipped in case of binding failure on construction.
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name, parameter);
if (binder.getTarget() != null) {
if (attribute == null) {
constructAttribute(binder, webRequest);
attribute = wrapAsOptionalIfNecessary(parameter, binder.getTarget());
}
if (!binder.getBindingResult().hasErrors()) {
if (!mavContainer.isBindingDisabled(name)) {
bindRequestParameters(binder, webRequest);
}
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
// Value type adaptation, also covering java.util.Optional
if (!parameter.getParameterType().isInstance(attribute)) {
@@ -189,165 +171,60 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
return attribute;
}
@Nullable
private static Object wrapAsOptionalIfNecessary(MethodParameter parameter, @Nullable Object target) {
return (parameter.getParameterType() == Optional.class ? Optional.ofNullable(target) : target);
}
/**
* Extension point to create the model attribute if not found in the model,
* with subsequent parameter binding through bean properties (unless suppressed).
* <p>The default implementation typically uses the unique public no-arg constructor
* if available but also handles a "primary constructor" approach for data classes:
* It understands the JavaBeans {@code ConstructorProperties} annotation as well as
* runtime-retained parameter names in the bytecode, associating request parameters
* with constructor arguments by name. If no such constructor is found, the default
* constructor will be used (even if not public), assuming subsequent bean property
* bindings through setter methods.
* <p>By default, as of 6.1 this method returns {@code null} in which case
* {@link org.springframework.validation.DataBinder#construct} is used instead
* to create the model attribute. The main purpose of this method then is to
* allow to create the model attribute in some other, alternative way.
* @param attributeName the name of the attribute (never {@code null})
* @param parameter the method parameter declaration
* @param binderFactory for creating WebDataBinder instance
* @param webRequest the current request
* @return the created model attribute (never {@code null})
* @throws BindException in case of constructor argument binding failure
* @throws Exception in case of constructor invocation failure
* @throws Exception in case of constructor instantiation failure
* @see #constructAttribute(Constructor, String, MethodParameter, WebDataBinderFactory, NativeWebRequest)
* @see BeanUtils#findPrimaryConstructor(Class)
*/
@Nullable
protected Object createAttribute(String attributeName, MethodParameter parameter,
WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception {
MethodParameter nestedParameter = parameter.nestedIfOptional();
Class<?> clazz = nestedParameter.getNestedParameterType();
Constructor<?> ctor = BeanUtils.getResolvableConstructor(clazz);
Object attribute = constructAttribute(ctor, attributeName, parameter, binderFactory, webRequest);
if (parameter != nestedParameter) {
attribute = Optional.of(attribute);
}
return attribute;
return null;
}
/**
* Construct a new attribute instance with the given constructor.
* <p>Called from
* {@link #createAttribute(String, MethodParameter, WebDataBinderFactory, NativeWebRequest)}
* after constructor resolution.
* @param ctor the constructor to use
* @param attributeName the name of the attribute (never {@code null})
* @param parameter the method parameter declaration
* @param binderFactory for creating WebDataBinder instance
* @param webRequest the current request
* @return the created model attribute (never {@code null})
* @throws BindException in case of constructor argument binding failure
* @throws Exception in case of constructor invocation failure
* @since 5.1
* @deprecated and not called; replaced by built-in support for
* constructor initialization in {@link org.springframework.validation.DataBinder}
*/
@SuppressWarnings("serial")
@Deprecated(since = "6.1", forRemoval = true)
protected Object constructAttribute(Constructor<?> ctor, String attributeName, MethodParameter parameter,
WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception {
if (ctor.getParameterCount() == 0) {
// A single default constructor -> clearly a standard JavaBeans arrangement.
return BeanUtils.instantiateClass(ctor);
}
// A single data class constructor -> resolve constructor arguments from request parameters.
String[] paramNames = BeanUtils.getParameterNames(ctor);
Class<?>[] paramTypes = ctor.getParameterTypes();
Object[] args = new Object[paramTypes.length];
WebDataBinder binder = binderFactory.createBinder(webRequest, null, attributeName, parameter);
String fieldDefaultPrefix = binder.getFieldDefaultPrefix();
String fieldMarkerPrefix = binder.getFieldMarkerPrefix();
boolean bindingFailure = false;
Set<String> failedParams = new HashSet<>(4);
for (int i = 0; i < paramNames.length; i++) {
String paramName = paramNames[i];
Class<?> paramType = paramTypes[i];
Object value = webRequest.getParameterValues(paramName);
// Since WebRequest#getParameter exposes a single-value parameter as an array
// with a single element, we unwrap the single value in such cases, analogous
// to WebExchangeDataBinder.addBindValue(Map<String, Object>, String, List<?>).
if (ObjectUtils.isArray(value) && Array.getLength(value) == 1) {
value = Array.get(value, 0);
}
if (value == null) {
if (fieldDefaultPrefix != null) {
value = webRequest.getParameter(fieldDefaultPrefix + paramName);
}
if (value == null) {
if (fieldMarkerPrefix != null && webRequest.getParameter(fieldMarkerPrefix + paramName) != null) {
value = binder.getEmptyValue(paramType);
}
else {
value = resolveConstructorArgument(paramName, paramType, webRequest);
}
}
}
try {
MethodParameter methodParam = MethodParameter.forFieldAwareConstructor(ctor, i, paramName);
if (value == null && methodParam.isOptional()) {
args[i] = (methodParam.getParameterType() == Optional.class ? Optional.empty() : null);
}
else {
args[i] = binder.convertIfNecessary(value, paramType, methodParam);
}
}
catch (TypeMismatchException ex) {
ex.initPropertyName(paramName);
args[i] = null;
failedParams.add(paramName);
binder.getBindingResult().recordFieldValue(paramName, paramType, value);
binder.getBindingErrorProcessor().processPropertyAccessException(ex, binder.getBindingResult());
bindingFailure = true;
}
}
if (bindingFailure) {
BindingResult result = binder.getBindingResult();
for (int i = 0; i < paramNames.length; i++) {
String paramName = paramNames[i];
if (!failedParams.contains(paramName)) {
Object value = args[i];
result.recordFieldValue(paramName, paramTypes[i], value);
validateValueIfApplicable(binder, parameter, ctor.getDeclaringClass(), paramName, value);
}
}
if (!parameter.isOptional()) {
try {
Object target = BeanUtils.instantiateClass(ctor, args);
throw new MethodArgumentNotValidException(parameter, result) {
@Override
public Object getTarget() {
return target;
}
};
}
catch (BeanInstantiationException ex) {
// swallow and proceed without target instance
}
}
throw new MethodArgumentNotValidException(parameter, result);
}
try {
return BeanUtils.instantiateClass(ctor, args);
}
catch (BeanInstantiationException ex) {
if (KotlinDetector.isKotlinType(ctor.getDeclaringClass()) &&
ex.getCause() instanceof NullPointerException cause) {
BindingResult result = binder.getBindingResult();
ObjectError error = new ObjectError(ctor.getName(), cause.getMessage());
result.addError(error);
throw new MethodArgumentNotValidException(parameter, result);
}
else {
throw ex;
}
}
throw new UnsupportedOperationException();
}
/**
* Extension point to bind the request to the target object.
* Extension point to create the attribute, binding the request to constructor args.
* @param binder the data binder instance to use for the binding
* @param request the current request
* @since 6.1
*/
protected void constructAttribute(WebDataBinder binder, NativeWebRequest request) {
((WebRequestDataBinder) binder).construct(request);
}
/**
* Extension point to bind the request to the target object via setters/fields.
* @param binder the data binder instance to use for the binding
* @param request the current request
*/
@@ -355,28 +232,16 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
((WebRequestDataBinder) binder).bind(request);
}
@Nullable
/**
* Resolve the value for a constructor argument.
* @deprecated and not called; replaced by built-in support for
* constructor initialization in {@link org.springframework.validation.DataBinder}
*/
@Deprecated(since = "6.1", forRemoval = true)
public Object resolveConstructorArgument(String paramName, Class<?> paramType, NativeWebRequest request)
throws Exception {
MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
if (multipartRequest != null) {
List<MultipartFile> files = multipartRequest.getFiles(paramName);
if (!files.isEmpty()) {
return (files.size() == 1 ? files.get(0) : files);
}
}
else if (StringUtils.startsWithIgnoreCase(
request.getHeader(HttpHeaders.CONTENT_TYPE), MediaType.MULTIPART_FORM_DATA_VALUE)) {
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
if (servletRequest != null && HttpMethod.POST.matches(servletRequest.getMethod())) {
List<Part> parts = StandardServletPartUtils.getParts(servletRequest, paramName);
if (!parts.isEmpty()) {
return (parts.size() == 1 ? parts.get(0) : parts);
}
}
}
return null;
throw new UnsupportedOperationException();
}
/**
@@ -401,38 +266,15 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
/**
* Validate the specified candidate value if applicable.
* <p>The default implementation checks for {@code @jakarta.validation.Valid},
* Spring's {@link org.springframework.validation.annotation.Validated},
* and custom annotations whose name starts with "Valid".
* @param binder the DataBinder to be used
* @param parameter the method parameter declaration
* @param targetType the target type
* @param fieldName the name of the field
* @param value the candidate value
* @since 5.1
* @see #validateIfApplicable(WebDataBinder, MethodParameter)
* @see SmartValidator#validateValue(Class, String, Object, Errors, Object...)
* @deprecated and not called; replaced by built-in support for
* constructor initialization in {@link org.springframework.validation.DataBinder}
*/
@Deprecated(since = "6.1", forRemoval = true)
protected void validateValueIfApplicable(WebDataBinder binder, MethodParameter parameter,
Class<?> targetType, String fieldName, @Nullable Object value) {
for (Annotation ann : parameter.getParameterAnnotations()) {
Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);
if (validationHints != null) {
for (Validator validator : binder.getValidators()) {
if (validator instanceof SmartValidator smartValidator) {
try {
smartValidator.validateValue(targetType, fieldName, value,
binder.getBindingResult(), validationHints);
}
catch (IllegalArgumentException ex) {
// No corresponding field on the target class...
}
}
}
break;
}
}
throw new UnsupportedOperationException();
}
/**