From 65ba865d7058a7beb54d9d9dfd519678c93641ec Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 24 Mar 2017 12:15:45 +0100 Subject: [PATCH] Support for populating model attributes through data class constructors Includes a new overloaded ModelAndView constructor with an HttpStatus argument, as well as a HandlerMethodArgumentResolverSupport refactoring (revised checkParameterType signature, actually implementing the HandlerMethodArgumentResolver interface). Issue: SPR-15199 --- .../ModelAttributeMethodProcessor.java | 76 ++++++++++++---- .../ModelAttributeMethodProcessorTests.java | 6 +- .../method/HandlerMethodArgumentResolver.java | 4 +- .../HandlerMethodArgumentResolverSupport.java | 48 +++++----- .../SyncHandlerMethodArgumentResolver.java | 11 ++- ...AbstractMessageReaderArgumentResolver.java | 9 +- .../AbstractNamedValueArgumentResolver.java | 29 +++--- ...bstractNamedValueSyncArgumentResolver.java | 28 +++--- .../CookieValueMethodArgumentResolver.java | 15 ++-- .../ErrorsMethodArgumentResolver.java | 15 ++-- ...ExpressionValueMethodArgumentResolver.java | 17 ++-- .../HttpEntityArgumentResolver.java | 13 ++- .../annotation/ModelArgumentResolver.java | 5 +- .../ModelAttributeMethodArgumentResolver.java | 87 +++++++++++++----- ...PathVariableMapMethodArgumentResolver.java | 3 +- .../PathVariableMethodArgumentResolver.java | 21 ++--- .../annotation/PrincipalArgumentResolver.java | 9 +- ...equestAttributeMethodArgumentResolver.java | 14 ++- .../RequestBodyArgumentResolver.java | 10 +-- ...equestHeaderMapMethodArgumentResolver.java | 3 +- .../RequestHeaderMethodArgumentResolver.java | 10 +-- ...RequestParamMapMethodArgumentResolver.java | 1 - .../RequestParamMethodArgumentResolver.java | 14 +-- .../ServerWebExchangeArgumentResolver.java | 5 +- ...essionAttributeMethodArgumentResolver.java | 14 ++- .../WebSessionArgumentResolver.java | 12 ++- .../MessageReaderArgumentResolverTests.java | 20 +++-- ...lAttributeMethodArgumentResolverTests.java | 72 ++++++++++++--- .../web/servlet/ModelAndView.java | 25 ++++-- ...nnotationControllerHandlerMethodTests.java | 89 +++++++++++++------ 30 files changed, 411 insertions(+), 274 deletions(-) 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 755de18e31..828a691dac 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 @@ -16,15 +16,20 @@ package org.springframework.web.method.annotation; +import java.beans.ConstructorProperties; import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanUtils; +import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.MethodParameter; +import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.util.Assert; import org.springframework.validation.BindException; import org.springframework.validation.Errors; import org.springframework.validation.annotation.Validated; @@ -52,10 +57,13 @@ import org.springframework.web.method.support.ModelAndViewContainer; * attribute with or without the presence of an {@code @ModelAttribute}. * * @author Rossen Stoyanchev + * @author Juergen Hoeller * @since 3.1 */ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler { + private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); + protected final Log logger = LogFactory.getLog(getClass()); private final boolean annotationNotRequired; @@ -65,7 +73,7 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol * Class constructor. * @param annotationNotRequired if "true", non-simple method arguments and * return values are considered model attributes with or without a - * {@code @ModelAttribute} annotation. + * {@code @ModelAttribute} annotation */ public ModelAttributeMethodProcessor(boolean annotationNotRequired) { this.annotationNotRequired = annotationNotRequired; @@ -89,8 +97,8 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol * with request values via data binding and optionally validated * if {@code @java.validation.Valid} is present on the argument. * @throws BindException if data binding and validation result in an error - * and the next method parameter is not of type {@link Errors}. - * @throws Exception if WebDataBinder initialization fails. + * and the next method parameter is not of type {@link Errors} + * @throws Exception if WebDataBinder initialization fails */ @Override public final Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, @@ -123,22 +131,54 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol mavContainer.removeAttributes(bindingResultModel); mavContainer.addAllAttributes(bindingResultModel); - return binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter); + return (parameter.getParameterType().isInstance(attribute) ? attribute : + binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter)); } /** - * Extension point to create the model attribute if not found in the model. - * The default implementation uses the default constructor. + * Extension point to create the model attribute if not found in the model, + * with subsequent parameter binding through bean properties (unless suppressed). + *

The default implementation uses the unique public no-arg constructor, if any, + * which may have arguments: It understands the JavaBeans {@link 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. * @param attributeName the name of the attribute (never {@code null}) - * @param methodParam the method parameter + * @param parameter the method parameter declaration * @param binderFactory for creating WebDataBinder instance - * @param request the current request + * @param webRequest the current request * @return the created model attribute (never {@code null}) */ - protected Object createAttribute(String attributeName, MethodParameter methodParam, - WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception { + protected Object createAttribute(String attributeName, MethodParameter parameter, + WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception { - return BeanUtils.instantiateClass(methodParam.getParameterType()); + Constructor[] ctors = parameter.getParameterType().getConstructors(); + if (ctors.length != 1) { + // No standard data class or standard JavaBeans arrangement -> + // defensively go with default constructor, expecting regular bean property bindings. + return BeanUtils.instantiateClass(parameter.getParameterType()); + } + Constructor ctor = ctors[0]; + 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. + 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); + Class[] paramTypes = ctor.getParameterTypes(); + Assert.state(paramNames.length == paramTypes.length, + () -> "Invalid number of parameter names: " + paramNames.length + " for constructor " + ctor); + Object[] args = new Object[paramTypes.length]; + WebDataBinder binder = binderFactory.createBinder(webRequest, null, attributeName); + for (int i = 0; i < paramNames.length; i++) { + args[i] = binder.convertIfNecessary( + webRequest.getParameterValues(paramNames[i]), paramTypes[i], new MethodParameter(ctor, i)); + } + return BeanUtils.instantiateClass(ctor, args); } /** @@ -156,10 +196,10 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol * Spring's {@link org.springframework.validation.annotation.Validated}, * and custom annotations whose name starts with "Valid". * @param binder the DataBinder to be used - * @param methodParam the method parameter + * @param parameter the method parameter declaration */ - protected void validateIfApplicable(WebDataBinder binder, MethodParameter methodParam) { - Annotation[] annotations = methodParam.getParameterAnnotations(); + protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) { + Annotation[] annotations = parameter.getParameterAnnotations(); for (Annotation ann : annotations) { Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class); if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) { @@ -174,12 +214,12 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol /** * Whether to raise a fatal bind exception on validation errors. * @param binder the data binder used to perform data binding - * @param methodParam the method argument + * @param parameter the method parameter declaration * @return {@code true} if the next method argument is not of type {@link Errors} */ - protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter methodParam) { - int i = methodParam.getParameterIndex(); - Class[] paramTypes = methodParam.getMethod().getParameterTypes(); + protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) { + int i = parameter.getParameterIndex(); + Class[] paramTypes = parameter.getMethod().getParameterTypes(); boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1])); return !hasBindingResult; } 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 8d0fb33361..a233edb70a 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2017 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. @@ -70,7 +70,7 @@ public class ModelAttributeMethodProcessorTests { @Before - public void setUp() throws Exception { + public void setup() throws Exception { this.request = new ServletWebRequest(new MockHttpServletRequest()); this.container = new ModelAndViewContainer(); this.processor = new ModelAttributeMethodProcessor(false); @@ -145,7 +145,7 @@ public class ModelAttributeMethodProcessorTests { } @Test - public void resovleArgumentViaDefaultConstructor() throws Exception { + public void resolveArgumentViaDefaultConstructor() throws Exception { WebDataBinder dataBinder = new WebRequestDataBinder(null); WebDataBinderFactory factory = mock(WebDataBinderFactory.class); given(factory.createBinder(any(), notNull(), eq("attrName"))).willReturn(dataBinder); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/HandlerMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/HandlerMethodArgumentResolver.java index fdf4446587..4a251fcb9d 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/HandlerMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/HandlerMethodArgumentResolver.java @@ -44,7 +44,7 @@ public interface HandlerMethodArgumentResolver { * @param exchange the current exchange * @return {@code Mono} for the argument value, possibly empty */ - Mono resolveArgument(MethodParameter parameter, BindingContext bindingContext, - ServerWebExchange exchange); + Mono resolveArgument( + MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/HandlerMethodArgumentResolverSupport.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/HandlerMethodArgumentResolverSupport.java index 13a576fecc..d55581c203 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/HandlerMethodArgumentResolverSupport.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/HandlerMethodArgumentResolverSupport.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.web.reactive.result.method; import java.lang.annotation.Annotation; @@ -25,14 +26,14 @@ import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.util.Assert; /** - * Base class for {@link HandlerMethodArgumentResolver} implementations with - * access to a {@code ReactiveAdapterRegistry} and methods to check for - * method parameter support. + * Base class for {@link HandlerMethodArgumentResolver} implementations with access to a + * {@code ReactiveAdapterRegistry} and methods to check for method parameter support. * * @author Rossen Stoyanchev + * @author Juergen Hoeller * @since 5.0 */ -public abstract class HandlerMethodArgumentResolverSupport { +public abstract class HandlerMethodArgumentResolverSupport implements HandlerMethodArgumentResolver { private final ReactiveAdapterRegistry adapterRegistry; @@ -55,12 +56,12 @@ public abstract class HandlerMethodArgumentResolverSupport { * Evaluate the {@code Predicate} on the the method parameter type or on * the generic type within a reactive type wrapper. */ - protected boolean checkParamType(MethodParameter param, Predicate> predicate) { - Class type = param.getParameterType(); + protected boolean checkParameterType(MethodParameter parameter, Predicate> predicate) { + Class type = parameter.getParameterType(); ReactiveAdapter adapter = getAdapterRegistry().getAdapter(type); if (adapter != null) { - assertHasValues(adapter, param); - type = param.nested().getNestedParameterType(); + assertHasValues(adapter, parameter); + type = parameter.nested().getNestedParameterType(); } return predicate.test(type); } @@ -77,25 +78,25 @@ public abstract class HandlerMethodArgumentResolverSupport { * {@code IllegalStateException} if the same matches the generic type * within a reactive type wrapper. */ - protected boolean checkParamTypeNoReactiveWrapper(MethodParameter param, Predicate> predicate) { - Class type = param.getParameterType(); + protected boolean checkParameterTypeNoReactiveWrapper(MethodParameter parameter, Predicate> predicate) { + Class type = parameter.getParameterType(); ReactiveAdapter adapter = getAdapterRegistry().getAdapter(type); if (adapter != null) { - assertHasValues(adapter, param); - type = param.nested().getNestedParameterType(); + assertHasValues(adapter, parameter); + type = parameter.nested().getNestedParameterType(); } if (predicate.test(type)) { if (adapter == null) { return true; } - throw getReactiveWrapperError(param); + throw buildReactiveWrapperException(parameter); } return false; } - private IllegalStateException getReactiveWrapperError(MethodParameter param) { + private IllegalStateException buildReactiveWrapperException(MethodParameter parameter) { return new IllegalStateException(getClass().getSimpleName() + - " doesn't support reactive type wrapper: " + param.getGenericParameterType()); + " doesn't support reactive type wrapper: " + parameter.getGenericParameterType()); } /** @@ -105,29 +106,28 @@ public abstract class HandlerMethodArgumentResolverSupport { * type within a reactive type wrapper. */ protected boolean checkAnnotatedParamNoReactiveWrapper( - MethodParameter param, Class annotationType, - BiPredicate> typePredicate) { + MethodParameter parameter, Class annotationType, BiPredicate> typePredicate) { - A annotation = param.getParameterAnnotation(annotationType); + A annotation = parameter.getParameterAnnotation(annotationType); if (annotation == null) { return false; } - param = param.nestedIfOptional(); - Class type = param.getNestedParameterType(); + parameter = parameter.nestedIfOptional(); + Class type = parameter.getNestedParameterType(); ReactiveAdapter adapter = getAdapterRegistry().getAdapter(type); if (adapter != null) { - assertHasValues(adapter, param); - param = param.nested(); - type = param.getNestedParameterType(); + assertHasValues(adapter, parameter); + parameter = parameter.nested(); + type = parameter.getNestedParameterType(); } if (typePredicate.test(annotation, type)) { if (adapter == null) { return true; } - throw getReactiveWrapperError(param); + throw buildReactiveWrapperException(parameter); } return false; diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/SyncHandlerMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/SyncHandlerMethodArgumentResolver.java index 5652412842..bc0392127f 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/SyncHandlerMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/SyncHandlerMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -33,15 +33,14 @@ import org.springframework.web.server.ServerWebExchange; */ public interface SyncHandlerMethodArgumentResolver extends HandlerMethodArgumentResolver { - /** * {@inheritDoc} *

By default this simply delegates to {@link #resolveArgumentValue} for * synchronous resolution. */ @Override - default Mono resolveArgument(MethodParameter parameter, BindingContext bindingContext, - ServerWebExchange exchange) { + default Mono resolveArgument( + MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange) { return Mono.justOrEmpty(resolveArgumentValue(parameter, bindingContext, exchange)); } @@ -53,7 +52,7 @@ public interface SyncHandlerMethodArgumentResolver extends HandlerMethodArgument * @param exchange the current exchange * @return an {@code Optional} with the resolved value, possibly empty */ - Optional resolveArgumentValue(MethodParameter parameter, BindingContext bindingContext, - ServerWebExchange exchange); + Optional resolveArgumentValue( + MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange); } 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 57587d28fc..a833004b5b 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -114,7 +114,6 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho } for (ServerHttpMessageReader reader : getMessageReaders()) { - if (reader.canRead(elementType, mediaType)) { Map readHints = Collections.emptyMap(); if (adapter != null && adapter.isMultiValue()) { @@ -171,9 +170,9 @@ public abstract class AbstractMessageReaderArgumentResolver extends HandlerMetho private Object[] extractValidationHints(MethodParameter parameter) { Annotation[] annotations = parameter.getParameterAnnotations(); for (Annotation ann : annotations) { - Validated validAnnot = AnnotationUtils.getAnnotation(ann, Validated.class); - if (validAnnot != null || ann.annotationType().getSimpleName().startsWith("Valid")) { - Object hints = (validAnnot != null ? validAnnot.value() : AnnotationUtils.getValue(ann)); + Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class); + if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) { + Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann)); return (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints}); } } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractNamedValueArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractNamedValueArgumentResolver.java index e41eac2ba4..8f9374f53a 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractNamedValueArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractNamedValueArgumentResolver.java @@ -32,7 +32,6 @@ import org.springframework.ui.Model; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.ValueConstants; import org.springframework.web.reactive.BindingContext; -import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolverSupport; import org.springframework.web.server.ServerErrorException; import org.springframework.web.server.ServerWebExchange; @@ -58,8 +57,7 @@ import org.springframework.web.server.ServerWebInputException; * @author Rossen Stoyanchev * @since 5.0 */ -public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodArgumentResolverSupport - implements HandlerMethodArgumentResolver { +public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodArgumentResolverSupport { private final ConfigurableBeanFactory configurableBeanFactory; @@ -69,23 +67,21 @@ public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodAr /** - * @param beanFactory a bean factory to use for resolving ${...} placeholder + * @param factory a bean factory to use for resolving ${...} placeholder * and #{...} SpEL expressions in default values, or {@code null} if default * values are not expected to contain expressions - * @param adapterRegistry for checking reactive type wrappers + * @param registry for checking reactive type wrappers */ - public AbstractNamedValueArgumentResolver(ConfigurableBeanFactory beanFactory, - ReactiveAdapterRegistry adapterRegistry) { - - super(adapterRegistry); - this.configurableBeanFactory = beanFactory; - this.expressionContext = (beanFactory != null ? new BeanExpressionContext(beanFactory, null) : null); + public AbstractNamedValueArgumentResolver(ConfigurableBeanFactory factory, ReactiveAdapterRegistry registry) { + super(registry); + this.configurableBeanFactory = factory; + this.expressionContext = (factory != null ? new BeanExpressionContext(factory, null) : null); } @Override - public Mono resolveArgument(MethodParameter parameter, BindingContext bindingContext, - ServerWebExchange exchange) { + public Mono resolveArgument( + MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange) { NamedValueInfo namedValueInfo = getNamedValueInfo(parameter); MethodParameter nestedParameter = parameter.nestedIfOptional(); @@ -175,8 +171,7 @@ public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodAr * @param exchange the current exchange * @return the resolved argument (may be {@code null}) */ - protected abstract Mono resolveName(String name, MethodParameter parameter, - ServerWebExchange exchange); + protected abstract Mono resolveName(String name, MethodParameter parameter, ServerWebExchange exchange); /** * Apply type conversion if necessary. @@ -277,8 +272,8 @@ public abstract class AbstractNamedValueArgumentResolver extends HandlerMethodAr * @param exchange the current exchange */ @SuppressWarnings("UnusedParameters") - protected void handleResolvedValue(Object arg, String name, MethodParameter parameter, - Model model, ServerWebExchange exchange) { + protected void handleResolvedValue( + Object arg, String name, MethodParameter parameter, Model model, ServerWebExchange exchange) { } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractNamedValueSyncArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractNamedValueSyncArgumentResolver.java index ed24987cac..f0a5e8989e 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractNamedValueSyncArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/AbstractNamedValueSyncArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -39,23 +39,20 @@ import org.springframework.web.server.ServerWebExchange; public abstract class AbstractNamedValueSyncArgumentResolver extends AbstractNamedValueArgumentResolver implements SyncHandlerMethodArgumentResolver { - /** - * @param beanFactory a bean factory to use for resolving ${...} + * @param factory a bean factory to use for resolving ${...} * placeholder and #{...} SpEL expressions in default values; * or {@code null} if default values are not expected to have expressions - * @param adapterRegistry for checking reactive type wrappers + * @param registry for checking reactive type wrappers */ - protected AbstractNamedValueSyncArgumentResolver(ConfigurableBeanFactory beanFactory, - ReactiveAdapterRegistry adapterRegistry) { - - super(beanFactory, adapterRegistry); + protected AbstractNamedValueSyncArgumentResolver(ConfigurableBeanFactory factory, ReactiveAdapterRegistry registry) { + super(factory, registry); } @Override - public Mono resolveArgument(MethodParameter parameter, BindingContext bindingContext, - ServerWebExchange exchange) { + public Mono resolveArgument( + MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange) { // Flip the default implementation from SyncHandlerMethodArgumentResolver: // instead of delegating to (sync) resolveArgumentValue, @@ -66,8 +63,8 @@ public abstract class AbstractNamedValueSyncArgumentResolver extends AbstractNam } @Override - public Optional resolveArgumentValue(MethodParameter parameter, - BindingContext context, ServerWebExchange exchange) { + public Optional resolveArgumentValue( + MethodParameter parameter, BindingContext context, ServerWebExchange exchange) { // This won't block since resolveName below doesn't Object value = resolveArgument(parameter, context, exchange).block(); @@ -76,16 +73,13 @@ public abstract class AbstractNamedValueSyncArgumentResolver extends AbstractNam } @Override - protected final Mono resolveName(String name, MethodParameter param, - ServerWebExchange exchange) { - + protected final Mono resolveName(String name, MethodParameter param, ServerWebExchange exchange) { return Mono.justOrEmpty(resolveNamedValue(name, param, exchange)); } /** * Actually resolve the value synchronously. */ - protected abstract Optional resolveNamedValue(String name, - MethodParameter param, ServerWebExchange exchange); + protected abstract Optional resolveNamedValue(String name, MethodParameter param, ServerWebExchange exchange); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/CookieValueMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/CookieValueMethodArgumentResolver.java index 78dd40e389..b5b2d550e0 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/CookieValueMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/CookieValueMethodArgumentResolver.java @@ -38,17 +38,14 @@ import org.springframework.web.server.ServerWebInputException; */ public class CookieValueMethodArgumentResolver extends AbstractNamedValueSyncArgumentResolver { - /** - * @param beanFactory a bean factory to use for resolving ${...} + * @param factory a bean factory to use for resolving ${...} * placeholder and #{...} SpEL expressions in default values; * or {@code null} if default values are not expected to contain expressions - * @param adapterRegistry for checking reactive type wrappers + * @param registry for checking reactive type wrappers */ - public CookieValueMethodArgumentResolver(ConfigurableBeanFactory beanFactory, - ReactiveAdapterRegistry adapterRegistry) { - - super(beanFactory, adapterRegistry); + public CookieValueMethodArgumentResolver(ConfigurableBeanFactory factory, ReactiveAdapterRegistry registry) { + super(factory, registry); } @@ -64,9 +61,7 @@ public class CookieValueMethodArgumentResolver extends AbstractNamedValueSyncArg } @Override - protected Optional resolveNamedValue(String name, MethodParameter parameter, - ServerWebExchange exchange) { - + protected Optional resolveNamedValue(String name, MethodParameter parameter, ServerWebExchange exchange) { HttpCookie cookie = exchange.getRequest().getCookies().getFirst(name); Class paramType = parameter.getNestedParameterType(); if (HttpCookie.class.isAssignableFrom(paramType)) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ErrorsMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ErrorsMethodArgumentResolver.java index 5c84ef0dcd..d8bc9ab150 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ErrorsMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ErrorsMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -41,9 +41,7 @@ import org.springframework.web.server.ServerWebExchange; * @author Rossen Stoyanchev * @since 5.0 */ -public class ErrorsMethodArgumentResolver extends HandlerMethodArgumentResolverSupport - implements HandlerMethodArgumentResolver { - +public class ErrorsMethodArgumentResolver extends HandlerMethodArgumentResolverSupport { public ErrorsMethodArgumentResolver(ReactiveAdapterRegistry registry) { super(registry); @@ -52,13 +50,13 @@ public class ErrorsMethodArgumentResolver extends HandlerMethodArgumentResolverS @Override public boolean supportsParameter(MethodParameter parameter) { - return checkParamTypeNoReactiveWrapper(parameter, Errors.class::isAssignableFrom); + return checkParameterTypeNoReactiveWrapper(parameter, Errors.class::isAssignableFrom); } @Override - public Mono resolveArgument(MethodParameter parameter, BindingContext context, - ServerWebExchange exchange) { + public Mono resolveArgument( + MethodParameter parameter, BindingContext context, ServerWebExchange exchange) { String name = getModelAttributeName(parameter); Object errors = context.getModel().asMap().get(BindingResult.MODEL_KEY_PREFIX + name); @@ -79,9 +77,8 @@ public class ErrorsMethodArgumentResolver extends HandlerMethodArgumentResolverS } private String getModelAttributeName(MethodParameter parameter) { - Assert.isTrue(parameter.getParameterIndex() > 0, - "Errors argument must be immediately after a model attribute argument."); + "Errors argument must be immediately after a model attribute argument"); int index = parameter.getParameterIndex() - 1; MethodParameter attributeParam = new MethodParameter(parameter.getMethod(), index); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ExpressionValueMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ExpressionValueMethodArgumentResolver.java index 3ac65ecec1..542b49f964 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ExpressionValueMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ExpressionValueMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -36,17 +36,14 @@ import org.springframework.web.server.ServerWebExchange; */ public class ExpressionValueMethodArgumentResolver extends AbstractNamedValueSyncArgumentResolver { - /** - * @param beanFactory a bean factory to use for resolving ${...} + * @param factory a bean factory to use for resolving ${...} * placeholder and #{...} SpEL expressions in default values; * or {@code null} if default values are not expected to contain expressions - * @param adapterRegistry for checking reactive type wrappers + * @param registry for checking reactive type wrappers */ - public ExpressionValueMethodArgumentResolver(ConfigurableBeanFactory beanFactory, - ReactiveAdapterRegistry adapterRegistry) { - - super(beanFactory, adapterRegistry); + public ExpressionValueMethodArgumentResolver(ConfigurableBeanFactory factory, ReactiveAdapterRegistry registry) { + super(factory, registry); } @@ -62,9 +59,7 @@ public class ExpressionValueMethodArgumentResolver extends AbstractNamedValueSyn } @Override - protected Optional resolveNamedValue(String name, MethodParameter parameter, - ServerWebExchange exchange) { - + protected Optional resolveNamedValue(String name, MethodParameter parameter, ServerWebExchange exchange) { // No name to resolve return Optional.empty(); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolver.java index 41cfd15c73..7fd0576083 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/HttpEntityArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -38,9 +38,7 @@ import org.springframework.web.server.ServerWebExchange; * @author Rossen Stoyanchev * @since 5.0 */ -public class HttpEntityArgumentResolver extends AbstractMessageReaderArgumentResolver - implements HandlerMethodArgumentResolver { - +public class HttpEntityArgumentResolver extends AbstractMessageReaderArgumentResolver { public HttpEntityArgumentResolver(List> readers, ReactiveAdapterRegistry registry) { @@ -51,16 +49,15 @@ public class HttpEntityArgumentResolver extends AbstractMessageReaderArgumentRes @Override public boolean supportsParameter(MethodParameter parameter) { - return checkParamTypeNoReactiveWrapper(parameter, + return checkParameterTypeNoReactiveWrapper(parameter, type -> HttpEntity.class.equals(type) || RequestEntity.class.equals(type)); } @Override - public Mono resolveArgument(MethodParameter parameter, BindingContext bindingContext, - ServerWebExchange exchange) { + public Mono resolveArgument( + MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange) { Class entityType = parameter.getParameterType(); - return readBody(parameter.nested(), false, bindingContext, exchange) .map(body -> createEntity(body, entityType, exchange.getRequest())) .defaultIfEmpty(createEntity(null, entityType, exchange.getRequest())); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelArgumentResolver.java index 4c97b57303..d34f3b87c8 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ModelArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -36,7 +36,6 @@ import org.springframework.web.server.ServerWebExchange; public class ModelArgumentResolver extends HandlerMethodArgumentResolverSupport implements SyncHandlerMethodArgumentResolver { - public ModelArgumentResolver(ReactiveAdapterRegistry adapterRegistry) { super(adapterRegistry); } @@ -44,7 +43,7 @@ public class ModelArgumentResolver extends HandlerMethodArgumentResolverSupport @Override public boolean supportsParameter(MethodParameter parameter) { - return checkParamTypeNoReactiveWrapper(parameter, Model.class::isAssignableFrom); + return checkParameterTypeNoReactiveWrapper(parameter, Model.class::isAssignableFrom); } @Override 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 89c50fdf54..3caef1a9f2 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 @@ -16,30 +16,34 @@ package org.springframework.web.reactive.result.method.annotation; +import java.beans.ConstructorProperties; import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.util.List; import java.util.Map; import reactor.core.publisher.Mono; import reactor.core.publisher.MonoProcessor; import org.springframework.beans.BeanUtils; +import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.MethodParameter; +import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ReactiveAdapter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.ui.Model; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; 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; import org.springframework.web.reactive.BindingContext; -import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolverSupport; import org.springframework.web.server.ServerWebExchange; @@ -58,10 +62,12 @@ import org.springframework.web.server.ServerWebExchange; * attribute with or without the presence of an {@code @ModelAttribute}. * * @author Rossen Stoyanchev + * @author Juergen Hoeller * @since 5.0 */ -public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentResolverSupport - implements HandlerMethodArgumentResolver { +public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentResolverSupport { + + private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); private final boolean useDefaultResolution; @@ -87,25 +93,25 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR return true; } else if (this.useDefaultResolution) { - return checkParamType(parameter, type -> !BeanUtils.isSimpleProperty(type)); + return checkParameterType(parameter, type -> !BeanUtils.isSimpleProperty(type)); } return false; } @Override - public Mono resolveArgument(MethodParameter parameter, BindingContext context, - ServerWebExchange exchange) { + public Mono resolveArgument( + MethodParameter parameter, BindingContext context, ServerWebExchange exchange) { ResolvableType type = ResolvableType.forMethodParameter(parameter); ReactiveAdapter adapter = getAdapterRegistry().getAdapter(type.resolve()); ResolvableType valueType = (adapter != null ? type.getGeneric(0) : type); Assert.state(adapter == null || !adapter.isMultiValue(), - getClass().getSimpleName() + " doesn't support multi-value reactive type wrapper: " + + () -> getClass().getSimpleName() + " doesn't support multi-value reactive type wrapper: " + parameter.getGenericParameterType()); String name = getAttributeName(valueType, parameter); - Mono valueMono = getAttributeMono(name, valueType, context.getModel()); + Mono valueMono = getAttributeMono(name, valueType, context, exchange); Map model = context.getModel().asMap(); MonoProcessor bindingResultMono = MonoProcessor.create(); @@ -140,22 +146,25 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR } private String getAttributeName(ResolvableType valueType, MethodParameter parameter) { - ModelAttribute annot = parameter.getParameterAnnotation(ModelAttribute.class); - if (annot != null && StringUtils.hasText(annot.value())) { - return annot.value(); + ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class); + if (ann != null && StringUtils.hasText(ann.value())) { + return ann.value(); } // TODO: Conventions does not deal with async wrappers return ClassUtils.getShortNameAsProperty(valueType.getRawClass()); } - private Mono getAttributeMono(String attributeName, ResolvableType attributeType, Model model) { - Object attribute = model.asMap().get(attributeName); + private Mono getAttributeMono( + String attributeName, ResolvableType attributeType, BindingContext context, ServerWebExchange exchange) { + + Object attribute = context.getModel().asMap().get(attributeName); if (attribute == null) { - attribute = BeanUtils.instantiateClass(attributeType.getRawClass()); + return createAttribute(attributeName, attributeType.getRawClass(), context, exchange); } + ReactiveAdapter adapterFrom = getAdapterRegistry().getAdapter(null, attribute); if (adapterFrom != null) { - Assert.isTrue(!adapterFrom.isMultiValue(), "Data binding supports single-value async types."); + Assert.isTrue(!adapterFrom.isMultiValue(), "Data binding only supports single-value async types"); return Mono.from(adapterFrom.toPublisher(attribute)); } else { @@ -163,10 +172,48 @@ public class ModelAttributeMethodArgumentResolver extends HandlerMethodArgumentR } } - private boolean hasErrorsArgument(MethodParameter methodParam) { - int i = methodParam.getParameterIndex(); - Class[] paramTypes = methodParam.getMethod().getParameterTypes(); - return paramTypes.length > i && Errors.class.isAssignableFrom(paramTypes[i + 1]); + private Mono createAttribute( + String attributeName, Class attributeType, BindingContext context, ServerWebExchange exchange) { + + Constructor[] ctors = attributeType.getConstructors(); + if (ctors.length != 1) { + // No standard data class or standard JavaBeans arrangement -> + // defensively go with default constructor, expecting regular bean property bindings. + return Mono.just(BeanUtils.instantiateClass(attributeType)); + } + Constructor ctor = ctors[0]; + if (ctor.getParameterCount() == 0) { + // A single default constructor -> clearly a standard JavaBeans arrangement. + return Mono.just(BeanUtils.instantiateClass(ctor)); + } + + // A single data class constructor -> resolve constructor arguments from request parameters. + return exchange.getRequestParams().then(requestParams -> { + 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); + Class[] paramTypes = ctor.getParameterTypes(); + 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); + for (int i = 0; i < paramNames.length; i++) { + List paramValues = requestParams.get(paramNames[i]); + Object paramValue = null; + if (paramValues != null) { + paramValue = (paramValues.size() == 1 ? paramValues.get(0) : + paramValues.toArray(new String[paramValues.size()])); + } + args[i] = binder.convertIfNecessary(paramValue, paramTypes[i], new MethodParameter(ctor, i)); + } + return Mono.fromSupplier(() -> BeanUtils.instantiateClass(ctor, args)); + }); + } + + private boolean hasErrorsArgument(MethodParameter parameter) { + int i = parameter.getParameterIndex(); + Class[] paramTypes = parameter.getMethod().getParameterTypes(); + return (paramTypes.length > i && Errors.class.isAssignableFrom(paramTypes[i + 1])); } private void validateIfApplicable(WebExchangeDataBinder binder, MethodParameter parameter) { diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/PathVariableMapMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/PathVariableMapMethodArgumentResolver.java index eb220cac59..3b175b0e30 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/PathVariableMapMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/PathVariableMapMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -43,7 +43,6 @@ import org.springframework.web.server.ServerWebExchange; public class PathVariableMapMethodArgumentResolver extends HandlerMethodArgumentResolverSupport implements SyncHandlerMethodArgumentResolver { - public PathVariableMapMethodArgumentResolver(ReactiveAdapterRegistry adapterRegistry) { super(adapterRegistry); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/PathVariableMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/PathVariableMethodArgumentResolver.java index 2a936afaf9..7f749efe5c 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/PathVariableMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/PathVariableMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -52,17 +52,14 @@ import org.springframework.web.server.ServerWebExchange; */ public class PathVariableMethodArgumentResolver extends AbstractNamedValueSyncArgumentResolver { - /** - * @param beanFactory a bean factory to use for resolving ${...} + * @param factory a bean factory to use for resolving ${...} * placeholder and #{...} SpEL expressions in default values; * or {@code null} if default values are not expected to contain expressions - * @param adapterRegistry for checking reactive type wrappers + * @param registry for checking reactive type wrappers */ - public PathVariableMethodArgumentResolver(ConfigurableBeanFactory beanFactory, - ReactiveAdapterRegistry adapterRegistry) { - - super(beanFactory, adapterRegistry); + public PathVariableMethodArgumentResolver(ConfigurableBeanFactory factory, ReactiveAdapterRegistry registry) { + super(factory, registry); } @@ -83,9 +80,7 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueSyncAr @Override @SuppressWarnings("unchecked") - protected Optional resolveNamedValue(String name, MethodParameter parameter, - ServerWebExchange exchange) { - + protected Optional resolveNamedValue(String name, MethodParameter parameter, ServerWebExchange exchange) { String attributeName = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE; return exchange.getAttribute(attributeName) .map(value -> ((Map) value).get(name)); @@ -98,8 +93,8 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueSyncAr @Override @SuppressWarnings("unchecked") - protected void handleResolvedValue(Object arg, String name, MethodParameter parameter, - Model model, ServerWebExchange exchange) { + protected void handleResolvedValue( + Object arg, String name, MethodParameter parameter, Model model, ServerWebExchange exchange) { // TODO: View.PATH_VARIABLES ? } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/PrincipalArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/PrincipalArgumentResolver.java index 54559ce9f7..d469f940d7 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/PrincipalArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/PrincipalArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -24,7 +24,6 @@ import org.springframework.core.MethodParameter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.util.Assert; import org.springframework.web.reactive.BindingContext; -import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver; import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolverSupport; import org.springframework.web.server.ServerWebExchange; @@ -35,9 +34,7 @@ import org.springframework.web.server.ServerWebExchange; * @since 5.0 * @see ServerWebExchangeArgumentResolver */ -public class PrincipalArgumentResolver extends HandlerMethodArgumentResolverSupport - implements HandlerMethodArgumentResolver { - +public class PrincipalArgumentResolver extends HandlerMethodArgumentResolverSupport { public PrincipalArgumentResolver(ReactiveAdapterRegistry adapterRegistry) { super(adapterRegistry); @@ -46,7 +43,7 @@ public class PrincipalArgumentResolver extends HandlerMethodArgumentResolverSupp @Override public boolean supportsParameter(MethodParameter parameter) { - return checkParamTypeNoReactiveWrapper(parameter, Principal.class::isAssignableFrom); + return checkParameterTypeNoReactiveWrapper(parameter, Principal.class::isAssignableFrom); } @Override diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestAttributeMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestAttributeMethodArgumentResolver.java index 079335c2f6..72c3f8a73d 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestAttributeMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestAttributeMethodArgumentResolver.java @@ -36,15 +36,13 @@ public class RequestAttributeMethodArgumentResolver extends AbstractNamedValueSy /** - * @param beanFactory a bean factory to use for resolving ${...} + * @param factory a bean factory to use for resolving ${...} * placeholder and #{...} SpEL expressions in default values; * or {@code null} if default values are not expected to have expressions - * @param adapterRegistry for checking reactive type wrappers + * @param registry for checking reactive type wrappers */ - public RequestAttributeMethodArgumentResolver(ConfigurableBeanFactory beanFactory, - ReactiveAdapterRegistry adapterRegistry) { - - super(beanFactory, adapterRegistry); + public RequestAttributeMethodArgumentResolver(ConfigurableBeanFactory factory, ReactiveAdapterRegistry registry) { + super(factory, registry); } @@ -61,9 +59,7 @@ public class RequestAttributeMethodArgumentResolver extends AbstractNamedValueSy } @Override - protected Optional resolveNamedValue(String name, MethodParameter parameter, - ServerWebExchange exchange) { - + protected Optional resolveNamedValue(String name, MethodParameter parameter, ServerWebExchange exchange) { return exchange.getAttribute(name); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolver.java index c512fb2403..56ce5977d6 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestBodyArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -43,9 +43,7 @@ import org.springframework.web.server.ServerWebInputException; * @author Rossen Stoyanchev * @since 5.0 */ -public class RequestBodyArgumentResolver extends AbstractMessageReaderArgumentResolver - implements HandlerMethodArgumentResolver { - +public class RequestBodyArgumentResolver extends AbstractMessageReaderArgumentResolver { public RequestBodyArgumentResolver(List> readers, ReactiveAdapterRegistry registry) { @@ -60,8 +58,8 @@ public class RequestBodyArgumentResolver extends AbstractMessageReaderArgumentRe } @Override - public Mono resolveArgument(MethodParameter param, BindingContext bindingContext, - ServerWebExchange exchange) { + public Mono resolveArgument( + MethodParameter param, BindingContext bindingContext, ServerWebExchange exchange) { RequestBody annotation = param.getParameterAnnotation(RequestBody.class); return readBody(param, annotation.required(), bindingContext, exchange); diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMapMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMapMethodArgumentResolver.java index d8d4e42575..db9ed78fe2 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMapMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMapMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -45,7 +45,6 @@ import org.springframework.web.server.ServerWebExchange; public class RequestHeaderMapMethodArgumentResolver extends HandlerMethodArgumentResolverSupport implements SyncHandlerMethodArgumentResolver { - public RequestHeaderMapMethodArgumentResolver(ReactiveAdapterRegistry adapterRegistry) { super(adapterRegistry); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMethodArgumentResolver.java index 5912ae39ec..754de6268c 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestHeaderMethodArgumentResolver.java @@ -47,15 +47,13 @@ import org.springframework.web.server.ServerWebInputException; public class RequestHeaderMethodArgumentResolver extends AbstractNamedValueSyncArgumentResolver { /** - * @param beanFactory a bean factory to use for resolving ${...} + * @param factory a bean factory to use for resolving ${...} * placeholder and #{...} SpEL expressions in default values; * or {@code null} if default values are not expected to have expressions - * @param adapterRegistry for checking reactive type wrappers + * @param registry for checking reactive type wrappers */ - public RequestHeaderMethodArgumentResolver(ConfigurableBeanFactory beanFactory, - ReactiveAdapterRegistry adapterRegistry) { - - super(beanFactory, adapterRegistry); + public RequestHeaderMethodArgumentResolver(ConfigurableBeanFactory factory, ReactiveAdapterRegistry registry) { + super(factory, registry); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMapMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMapMethodArgumentResolver.java index 3cc13359f4..7635a55f3c 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMapMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMapMethodArgumentResolver.java @@ -48,7 +48,6 @@ import org.springframework.web.server.ServerWebExchange; public class RequestParamMapMethodArgumentResolver extends HandlerMethodArgumentResolverSupport implements SyncHandlerMethodArgumentResolver { - public RequestParamMapMethodArgumentResolver(ReactiveAdapterRegistry adapterRegistry) { super(adapterRegistry); } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolver.java index 193cb5436c..c75f0819d1 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/RequestParamMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -59,19 +59,19 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueSyncAr /** * Class constructor with a default resolution mode flag. - * @param beanFactory a bean factory used for resolving ${...} placeholder + * @param factory a bean factory used for resolving ${...} placeholder * and #{...} SpEL expressions in default values, or {@code null} if default * values are not expected to contain expressions - * @param adapterRegistry for checking reactive type wrappers + * @param registry for checking reactive type wrappers * @param useDefaultResolution in default resolution mode a method argument * that is a simple type, as defined in {@link BeanUtils#isSimpleProperty}, * is treated as a request parameter even if it isn't annotated, the * request parameter name is derived from the method parameter name. */ - public RequestParamMethodArgumentResolver(ConfigurableBeanFactory beanFactory, - ReactiveAdapterRegistry adapterRegistry, boolean useDefaultResolution) { + public RequestParamMethodArgumentResolver( + ConfigurableBeanFactory factory, ReactiveAdapterRegistry registry, boolean useDefaultResolution) { - super(beanFactory, adapterRegistry); + super(factory, registry); this.useDefaultResolution = useDefaultResolution; } @@ -82,7 +82,7 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueSyncAr return true; } else if (this.useDefaultResolution) { - return checkParamTypeNoReactiveWrapper(param, BeanUtils::isSimpleProperty); + return checkParameterTypeNoReactiveWrapper(param, BeanUtils::isSimpleProperty); } return false; } diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ServerWebExchangeArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ServerWebExchangeArgumentResolver.java index 6a095a2452..d748f626e1 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ServerWebExchangeArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ServerWebExchangeArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -48,7 +48,6 @@ import org.springframework.web.server.ServerWebExchange; public class ServerWebExchangeArgumentResolver extends HandlerMethodArgumentResolverSupport implements SyncHandlerMethodArgumentResolver { - public ServerWebExchangeArgumentResolver(ReactiveAdapterRegistry adapterRegistry) { super(adapterRegistry); } @@ -56,7 +55,7 @@ public class ServerWebExchangeArgumentResolver extends HandlerMethodArgumentReso @Override public boolean supportsParameter(MethodParameter parameter) { - return checkParamTypeNoReactiveWrapper(parameter, + return checkParameterTypeNoReactiveWrapper(parameter, type -> ServerWebExchange.class.isAssignableFrom(type) || ServerHttpRequest.class.isAssignableFrom(type) || ServerHttpResponse.class.isAssignableFrom(type) || diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/SessionAttributeMethodArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/SessionAttributeMethodArgumentResolver.java index bb557c1b3b..5b208f46c0 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/SessionAttributeMethodArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/SessionAttributeMethodArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.web.reactive.result.method.annotation; import java.util.Optional; @@ -36,11 +37,8 @@ import org.springframework.web.server.ServerWebInputException; */ public class SessionAttributeMethodArgumentResolver extends AbstractNamedValueArgumentResolver { - - public SessionAttributeMethodArgumentResolver(ConfigurableBeanFactory beanFactory, - ReactiveAdapterRegistry adapterRegistry) { - - super(beanFactory, adapterRegistry); + public SessionAttributeMethodArgumentResolver(ConfigurableBeanFactory factory, ReactiveAdapterRegistry registry) { + super(factory, registry); } @@ -56,9 +54,7 @@ public class SessionAttributeMethodArgumentResolver extends AbstractNamedValueAr } @Override - protected Mono resolveName(String name, MethodParameter parameter, - ServerWebExchange exchange) { - + protected Mono resolveName(String name, MethodParameter parameter, ServerWebExchange exchange) { return exchange.getSession() .map(session -> session.getAttribute(name)) .filter(Optional::isPresent) diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/WebSessionArgumentResolver.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/WebSessionArgumentResolver.java index 265a64a804..d5895973e8 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/WebSessionArgumentResolver.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/WebSessionArgumentResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -34,9 +34,7 @@ import org.springframework.web.server.WebSession; * @since 5.0 * @see ServerWebExchangeArgumentResolver */ -public class WebSessionArgumentResolver extends HandlerMethodArgumentResolverSupport - implements HandlerMethodArgumentResolver { - +public class WebSessionArgumentResolver extends HandlerMethodArgumentResolverSupport { public WebSessionArgumentResolver(ReactiveAdapterRegistry adapterRegistry) { super(adapterRegistry); @@ -45,12 +43,12 @@ public class WebSessionArgumentResolver extends HandlerMethodArgumentResolverSup @Override public boolean supportsParameter(MethodParameter parameter) { - return checkParamTypeNoReactiveWrapper(parameter, WebSession.class::isAssignableFrom); + return checkParameterTypeNoReactiveWrapper(parameter, WebSession.class::isAssignableFrom); } @Override - public Mono resolveArgument(MethodParameter parameter, BindingContext context, - ServerWebExchange exchange) { + public Mono resolveArgument( + MethodParameter parameter, BindingContext context, ServerWebExchange exchange) { Assert.isAssignable(WebSession.class, parameter.getParameterType()); return exchange.getSession().cast(Object.class); diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageReaderArgumentResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageReaderArgumentResolverTests.java index d231396765..7cc280bcc4 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageReaderArgumentResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/MessageReaderArgumentResolverTests.java @@ -56,12 +56,9 @@ import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebInputException; import org.springframework.web.server.UnsupportedMediaTypeStatusException; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.springframework.core.ResolvableType.forClassWithGenerics; -import static org.springframework.mock.http.server.reactive.test.MockServerHttpRequest.post; +import static org.junit.Assert.*; +import static org.springframework.core.ResolvableType.*; +import static org.springframework.mock.http.server.reactive.test.MockServerHttpRequest.*; /** * Unit tests for {@link AbstractMessageReaderArgumentResolver}. @@ -307,7 +304,16 @@ public class MessageReaderArgumentResolverTests { private AbstractMessageReaderArgumentResolver resolver(Decoder... decoders) { List> readers = new ArrayList<>(); Arrays.asList(decoders).forEach(decoder -> readers.add(new DecoderHttpMessageReader<>(decoder))); - return new AbstractMessageReaderArgumentResolver(readers) {}; + return new AbstractMessageReaderArgumentResolver(readers) { + @Override + public boolean supportsParameter(MethodParameter parameter) { + return false; + } + @Override + public Mono resolveArgument(MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange) { + return null; + } + }; } diff --git a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolverTests.java b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolverTests.java index ab97efc882..6a1998b308 100644 --- a/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolverTests.java +++ b/spring-webflux/src/test/java/org/springframework/web/reactive/result/method/annotation/ModelAttributeMethodArgumentResolverTests.java @@ -42,16 +42,13 @@ import org.springframework.web.method.ResolvableMethod; import org.springframework.web.reactive.BindingContext; import org.springframework.web.server.ServerWebExchange; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; /** * Unit tests for {@link ModelAttributeMethodArgumentResolver}. * * @author Rossen Stoyanchev + * @author Juergen Hoeller */ public class ModelAttributeMethodArgumentResolverTests { @@ -116,7 +113,6 @@ public class ModelAttributeMethodArgumentResolverTests { @Test public void createAndBindToMono() throws Exception { - MethodParameter parameter = this.testMethod .annotNotPresent(ModelAttribute.class).arg(Mono.class, Foo.class); @@ -130,7 +126,6 @@ public class ModelAttributeMethodArgumentResolverTests { @Test public void createAndBindToSingle() throws Exception { - MethodParameter parameter = this.testMethod .annotPresent(ModelAttribute.class).arg(Single.class, Foo.class); @@ -211,6 +206,7 @@ public class ModelAttributeMethodArgumentResolverTests { Foo foo = valueExtractor.apply(value); assertEquals("Robert", foo.getName()); + assertEquals(25, foo.getAge()); String key = "foo"; String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + key; @@ -231,7 +227,6 @@ public class ModelAttributeMethodArgumentResolverTests { @Test @SuppressWarnings("unchecked") public void validationErrorToMono() throws Exception { - MethodParameter parameter = this.testMethod .annotNotPresent(ModelAttribute.class).arg(Mono.class, Foo.class); @@ -246,7 +241,6 @@ public class ModelAttributeMethodArgumentResolverTests { @Test public void validationErrorToSingle() throws Exception { - MethodParameter parameter = this.testMethod .annotPresent(ModelAttribute.class).arg(Single.class, Foo.class); @@ -264,7 +258,6 @@ public class ModelAttributeMethodArgumentResolverTests { ServerWebExchange exchange = postForm("age=invalid"); Mono mono = createResolver().resolveArgument(param, this.bindContext, exchange); - mono = valueMonoExtractor.apply(mono); StepVerifier.create(mono) @@ -277,6 +270,31 @@ public class ModelAttributeMethodArgumentResolverTests { .verify(); } + @Test + public void bindDataClass() throws Exception { + testBindBar(this.testMethod.annotNotPresent(ModelAttribute.class).arg(Bar.class)); + } + + private void testBindBar(MethodParameter param) throws Exception { + Object value = createResolver() + .resolveArgument(param, this.bindContext, postForm("name=Robert&age=25&count=1")) + .block(Duration.ZERO); + + Bar bar = (Bar) value; + assertEquals("Robert", bar.getName()); + assertEquals(25, bar.getAge()); + assertEquals(1, bar.getCount()); + + String key = "bar"; + String bindingResultKey = BindingResult.MODEL_KEY_PREFIX + key; + + Map map = bindContext.getModel().asMap(); + assertEquals(map.toString(), 2, map.size()); + assertSame(bar, map.get(key)); + assertNotNull(map.get(bindingResultKey)); + assertTrue(map.get(bindingResultKey) instanceof BindingResult); + } + private ModelAttributeMethodArgumentResolver createResolver() { return new ModelAttributeMethodArgumentResolver(new ReactiveAdapterRegistry(), false); @@ -298,7 +316,8 @@ public class ModelAttributeMethodArgumentResolverTests { Foo fooNotAnnotated, String stringNotAnnotated, Mono monoNotAnnotated, - Mono monoStringNotAnnotated) { + Mono monoStringNotAnnotated, + Bar barNotAnnotated) { } @@ -333,4 +352,35 @@ public class ModelAttributeMethodArgumentResolverTests { } } + + private static class Bar { + + private final String name; + + private final int age; + + private int count; + + public Bar(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public int getAge() { + return this.age; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + } + } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/ModelAndView.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/ModelAndView.java index 5323c1a8f4..237526d0c4 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/ModelAndView.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/ModelAndView.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -89,7 +89,7 @@ public class ModelAndView { } /** - * Creates new ModelAndView given a view name and a model. + * Create a new ModelAndView given a view name and a model. * @param viewName name of the View to render, to be resolved * by the DispatcherServlet's ViewResolver * @param model Map of model names (Strings) to model objects @@ -104,7 +104,7 @@ public class ModelAndView { } /** - * Creates new ModelAndView given a View object and a model. + * Create a new ModelAndView given a View object and a model. * Note: the supplied model data is copied into the internal * storage of this class. You should not consider to modify the supplied * Map after supplying it to this class @@ -121,14 +121,27 @@ public class ModelAndView { } /** - * Creates new ModelAndView given a view name, model, and status. + * Create a new ModelAndView given a view name and HTTP status. + * @param viewName name of the View to render, to be resolved + * by the DispatcherServlet's ViewResolver + * @param status an HTTP status code to use for the response + * (to be set just prior to View rendering) + * @since 4.3.8 + */ + public ModelAndView(String viewName, HttpStatus status) { + this.view = viewName; + this.status = status; + } + + /** + * Create a new ModelAndView given a view name, model, and HTTP status. * @param viewName name of the View to render, to be resolved * by the DispatcherServlet's ViewResolver * @param model Map of model names (Strings) to model objects * (Objects). Model entries may not be {@code null}, but the * model Map may be {@code null} if there is no model data. - * @param status an alternative status code to use for the response; - * The response status is set just prior to View rendering. + * @param status an HTTP status code to use for the response + * (to be set just prior to View rendering) * @since 4.3 */ public ModelAndView(String viewName, Map model, HttpStatus status) { diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java index f21a23f58c..42fe205d26 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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.servlet.mvc.method.annotation; +import java.beans.ConstructorProperties; import java.beans.PropertyEditorSupport; import java.io.IOException; import java.io.Serializable; @@ -140,14 +141,7 @@ import org.springframework.web.servlet.mvc.support.RedirectAttributes; import org.springframework.web.servlet.support.RequestContextUtils; import org.springframework.web.servlet.view.InternalResourceViewResolver; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; /** * @author Rossen Stoyanchev @@ -283,15 +277,13 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl }, NestedSetController.class); MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPath.do"); - request.addParameter("testBeanSet", new String[] {"1", "2"}); + request.addParameter("testBeanSet", "1", "2"); MockHttpServletResponse response = new MockHttpServletResponse(); getServlet().service(request, response); assertEquals("[1, 2]-org.springframework.tests.sample.beans.TestBean", response.getContentAsString()); } - // SPR-12903 - - @Test + @Test // SPR-12903 public void pathVariableWithCustomConverter() throws Exception { initServlet(new ApplicationContextInitializer() { @Override @@ -1244,9 +1236,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl assertEquals("myParam-42", response.getContentAsString()); } - // SPR-9062 - - @Test + @Test // SPR-9062 public void ambiguousPathAndRequestMethod() throws Exception { initServletWithControllers(AmbiguousPathAndRequestMethodController.class); @@ -1498,9 +1488,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl assertEquals(405, response.getStatus()); } - // SPR-8536 - - @Test + @Test // SPR-8536 public void testHeadersCondition() throws Exception { initServletWithControllers(HeadersConditionController.class); @@ -1571,7 +1559,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl assertTrue(RequestContextUtils.getOutputFlashMap(request).isEmpty()); } - @Test // SPR-15176 + @Test // SPR-15176 public void flashAttributesWithResponseEntity() throws Exception { initServletWithControllers(RedirectAttributesController.class); @@ -1831,6 +1819,31 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl assertTrue(response.getContentAsByteArray().length == 0); } + @Test + public void dataClassBinding() throws ServletException, IOException { + initServletWithControllers(DataClassController.class); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bind"); + request.addParameter("param1", "value1"); + request.addParameter("param2", "2"); + MockHttpServletResponse response = new MockHttpServletResponse(); + getServlet().service(request, response); + assertEquals("value1-2-0", response.getContentAsString()); + } + + @Test + public void dataClassBindingWithAdditionalSetter() throws ServletException, IOException { + initServletWithControllers(DataClassController.class); + + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bind"); + request.addParameter("param1", "value1"); + request.addParameter("param2", "2"); + request.addParameter("param3", "3"); + MockHttpServletResponse response = new MockHttpServletResponse(); + getServlet().service(request, response); + assertEquals("value1-2-3", response.getContentAsString()); + } + @Controller static class ControllerWithEmptyValueMapping { @@ -2727,15 +2740,12 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @XmlRootElement public static class A { - } @XmlRootElement public static class B { - } - public static class NotReadableMessageConverter implements HttpMessageConverter { @Override @@ -3313,7 +3323,6 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl public HttpHeaders createNoHeader() throws URISyntaxException { return new HttpHeaders(); } - } @RestController @@ -3345,7 +3354,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @RequestMapping("/path") public ModelAndView methodWithHttpStatus(MyEntity object) { - return new ModelAndView("view", new ModelMap(), HttpStatus.UNPROCESSABLE_ENTITY); + return new ModelAndView("view", HttpStatus.UNPROCESSABLE_ENTITY); } @RequestMapping("/exception") @@ -3355,7 +3364,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl @ExceptionHandler(TestException.class) public ModelAndView handleException() { - return new ModelAndView("view", new ModelMap(), HttpStatus.UNPROCESSABLE_ENTITY); + return new ModelAndView("view", HttpStatus.UNPROCESSABLE_ENTITY); } @SuppressWarnings("serial") @@ -3363,4 +3372,32 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl } } + public static class DataClass { + + public final String param1; + + public final int param2; + + public int param3; + + @ConstructorProperties({"param1", "param2"}) + public DataClass(String param1, int p2) { + this.param1 = param1; + this.param2 = p2; + } + + public void setParam3(int param3) { + this.param3 = param3; + } + } + + @RestController + public static class DataClassController { + + @RequestMapping("/bind") + public String handle(DataClass data) { + return data.param1 + "-" + data.param2 + "-" + data.param3; + } + } + }