Support for reactive controller @InitBinder methods
Issue: SPR-14543
This commit is contained in:
parent
0b76b6d7e9
commit
3da0295c12
|
@ -15,11 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package org.springframework.web.reactive.result.method;
|
package org.springframework.web.reactive.result.method;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
|
|
||||||
import org.springframework.beans.TypeConverter;
|
import org.springframework.beans.TypeConverter;
|
||||||
import org.springframework.beans.TypeMismatchException;
|
|
||||||
import org.springframework.core.MethodParameter;
|
|
||||||
import org.springframework.ui.ModelMap;
|
import org.springframework.ui.ModelMap;
|
||||||
import org.springframework.validation.support.BindingAwareModelMap;
|
import org.springframework.validation.support.BindingAwareModelMap;
|
||||||
import org.springframework.web.bind.WebDataBinder;
|
import org.springframework.web.bind.WebDataBinder;
|
||||||
|
@ -27,6 +23,7 @@ import org.springframework.web.bind.WebExchangeDataBinder;
|
||||||
import org.springframework.web.bind.support.WebBindingInitializer;
|
import org.springframework.web.bind.support.WebBindingInitializer;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A context for binding requests to method arguments that provides access to
|
* A context for binding requests to method arguments that provides access to
|
||||||
* the default model, data binding, validation, and type conversion.
|
* the default model, data binding, validation, and type conversion.
|
||||||
|
@ -34,7 +31,7 @@ import org.springframework.web.server.ServerWebExchange;
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @since 5.0
|
* @since 5.0
|
||||||
*/
|
*/
|
||||||
public class BindingContext implements TypeConverter {
|
public class BindingContext {
|
||||||
|
|
||||||
private final ModelMap model = new BindingAwareModelMap();
|
private final ModelMap model = new BindingAwareModelMap();
|
||||||
|
|
||||||
|
@ -68,46 +65,50 @@ public class BindingContext implements TypeConverter {
|
||||||
return this.model;
|
return this.model;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a {@link WebExchangeDataBinder} for the given object.
|
|
||||||
* @param exchange the current exchange
|
|
||||||
* @param target the object to create a data binder for, or {@code null} if
|
|
||||||
* creating a binder for a simple type
|
|
||||||
* @param objectName the name of the target object
|
|
||||||
* @return a Mono for the created {@link WebDataBinder} instance
|
|
||||||
*/
|
|
||||||
public WebExchangeDataBinder createBinder(ServerWebExchange exchange, Object target,
|
|
||||||
String objectName) {
|
|
||||||
|
|
||||||
WebExchangeDataBinder dataBinder = createBinderInstance(target, objectName);
|
/**
|
||||||
|
* Create a {@link WebExchangeDataBinder} for applying data binding, type
|
||||||
|
* conversion, and validation on the given "target" object.
|
||||||
|
*
|
||||||
|
* @param exchange the current exchange
|
||||||
|
* @param target the object to create a data binder for
|
||||||
|
* @param name the name of the target object
|
||||||
|
* @return the {@link WebDataBinder} instance
|
||||||
|
*/
|
||||||
|
public WebExchangeDataBinder createDataBinder(ServerWebExchange exchange, Object target, String name) {
|
||||||
|
WebExchangeDataBinder dataBinder = createBinderInstance(target, name);
|
||||||
if (this.initializer != null) {
|
if (this.initializer != null) {
|
||||||
this.initializer.initBinder(dataBinder);
|
this.initializer.initBinder(dataBinder);
|
||||||
}
|
}
|
||||||
return initBinder(dataBinder, exchange);
|
return initDataBinder(dataBinder, exchange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link WebExchangeDataBinder} without a "target" object, i.e.
|
||||||
|
* for applying type conversion to simple types.
|
||||||
|
*
|
||||||
|
* @param exchange the current exchange
|
||||||
|
* @param name the name of the target object
|
||||||
|
* @return a Mono for the created {@link WebDataBinder} instance
|
||||||
|
*/
|
||||||
|
public WebExchangeDataBinder createDataBinder(ServerWebExchange exchange, String name) {
|
||||||
|
return createDataBinder(exchange, null, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the data binder instance.
|
||||||
|
*/
|
||||||
protected WebExchangeDataBinder createBinderInstance(Object target, String objectName) {
|
protected WebExchangeDataBinder createBinderInstance(Object target, String objectName) {
|
||||||
return new WebExchangeDataBinder(target, objectName);
|
return new WebExchangeDataBinder(target, objectName);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected WebExchangeDataBinder initBinder(WebExchangeDataBinder binder, ServerWebExchange exchange) {
|
/**
|
||||||
|
* Initialize the data binder instance for the given exchange.
|
||||||
|
*/
|
||||||
|
protected WebExchangeDataBinder initDataBinder(WebExchangeDataBinder binder,
|
||||||
|
ServerWebExchange exchange) {
|
||||||
|
|
||||||
return binder;
|
return binder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public <T> T convertIfNecessary(Object value, Class<T> requiredType) throws TypeMismatchException {
|
|
||||||
return this.simpleValueTypeConverter.convertIfNecessary(value, requiredType);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> T convertIfNecessary(Object value, Class<T> requiredType, MethodParameter methodParam)
|
|
||||||
throws TypeMismatchException {
|
|
||||||
|
|
||||||
return this.simpleValueTypeConverter.convertIfNecessary(value, requiredType, methodParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> T convertIfNecessary(Object value, Class<T> requiredType, Field field)
|
|
||||||
throws TypeMismatchException {
|
|
||||||
|
|
||||||
return this.simpleValueTypeConverter.convertIfNecessary(value, requiredType, field);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,11 +67,24 @@ public class InvocableHandlerMethod extends HandlerMethod {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void setHandlerMethodArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
|
/**
|
||||||
|
* Set {@link HandlerMethodArgumentResolver}s to use to use for resolving
|
||||||
|
* method argument values.
|
||||||
|
*/
|
||||||
|
public void setArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
|
||||||
this.resolvers.clear();
|
this.resolvers.clear();
|
||||||
this.resolvers.addAll(resolvers);
|
this.resolvers.addAll(resolvers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the ParameterNameDiscoverer for resolving parameter names when needed
|
||||||
|
* (e.g. default request attribute name).
|
||||||
|
* <p>Default is a {@link DefaultParameterNameDiscoverer}.
|
||||||
|
*/
|
||||||
|
public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {
|
||||||
|
this.parameterNameDiscoverer = parameterNameDiscoverer;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Method getBridgedMethod() {
|
protected Method getBridgedMethod() {
|
||||||
return super.getBridgedMethod();
|
return super.getBridgedMethod();
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package org.springframework.web.reactive.result.method;
|
package org.springframework.web.reactive.result.method;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
@ -48,11 +49,11 @@ public class SyncInvocableHandlerMethod extends InvocableHandlerMethod {
|
||||||
* all resolvers are {@link SyncHandlerMethodArgumentResolver}.
|
* all resolvers are {@link SyncHandlerMethodArgumentResolver}.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void setHandlerMethodArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
|
public void setArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
|
||||||
resolvers.forEach(resolver ->
|
resolvers.forEach(resolver ->
|
||||||
Assert.isInstanceOf(SyncHandlerMethodArgumentResolver.class, resolver,
|
Assert.isInstanceOf(SyncHandlerMethodArgumentResolver.class, resolver,
|
||||||
"Expected sync argument resolver: " + resolver.getClass().getName()));
|
"Expected sync argument resolver: " + resolver.getClass().getName()));
|
||||||
super.setHandlerMethodArgumentResolvers(resolvers);
|
super.setArgumentResolvers(resolvers);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -213,7 +213,7 @@ public abstract class AbstractMessageReaderArgumentResolver {
|
||||||
MethodParameter param, BindingContext binding, ServerWebExchange exchange) {
|
MethodParameter param, BindingContext binding, ServerWebExchange exchange) {
|
||||||
|
|
||||||
String name = Conventions.getVariableNameForParameter(param);
|
String name = Conventions.getVariableNameForParameter(param);
|
||||||
WebExchangeDataBinder binder = binding.createBinder(exchange, target, name);
|
WebExchangeDataBinder binder = binding.createDataBinder(exchange, target, name);
|
||||||
binder.validate(validationHints);
|
binder.validate(validationHints);
|
||||||
if (binder.getBindingResult().hasErrors()) {
|
if (binder.getBindingResult().hasErrors()) {
|
||||||
throw new ServerWebInputException("Validation failed", param);
|
throw new ServerWebInputException("Validation failed", param);
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.springframework.beans.factory.config.BeanExpressionResolver;
|
||||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||||
import org.springframework.core.MethodParameter;
|
import org.springframework.core.MethodParameter;
|
||||||
import org.springframework.ui.ModelMap;
|
import org.springframework.ui.ModelMap;
|
||||||
|
import org.springframework.web.bind.WebDataBinder;
|
||||||
import org.springframework.web.bind.annotation.ValueConstants;
|
import org.springframework.web.bind.annotation.ValueConstants;
|
||||||
import org.springframework.web.reactive.result.method.BindingContext;
|
import org.springframework.web.reactive.result.method.BindingContext;
|
||||||
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
|
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
|
||||||
|
@ -93,7 +94,7 @@ public abstract class AbstractNamedValueArgumentResolver implements HandlerMetho
|
||||||
if ("".equals(arg) && namedValueInfo.defaultValue != null) {
|
if ("".equals(arg) && namedValueInfo.defaultValue != null) {
|
||||||
arg = resolveStringValue(namedValueInfo.defaultValue);
|
arg = resolveStringValue(namedValueInfo.defaultValue);
|
||||||
}
|
}
|
||||||
arg = applyConversion(arg, parameter, bindingContext);
|
arg = applyConversion(arg, namedValueInfo, parameter, bindingContext, exchange);
|
||||||
handleResolvedValue(arg, namedValueInfo.name, parameter, model, exchange);
|
handleResolvedValue(arg, namedValueInfo.name, parameter, model, exchange);
|
||||||
return arg;
|
return arg;
|
||||||
})
|
})
|
||||||
|
@ -168,9 +169,12 @@ public abstract class AbstractNamedValueArgumentResolver implements HandlerMetho
|
||||||
protected abstract Mono<Object> resolveName(String name, MethodParameter parameter,
|
protected abstract Mono<Object> resolveName(String name, MethodParameter parameter,
|
||||||
ServerWebExchange exchange);
|
ServerWebExchange exchange);
|
||||||
|
|
||||||
private Object applyConversion(Object value, MethodParameter parameter, BindingContext bindingContext) {
|
private Object applyConversion(Object value, NamedValueInfo namedValueInfo, MethodParameter parameter,
|
||||||
|
BindingContext bindingContext, ServerWebExchange exchange) {
|
||||||
|
|
||||||
|
WebDataBinder binder = bindingContext.createDataBinder(exchange, namedValueInfo.name);
|
||||||
try {
|
try {
|
||||||
value = bindingContext.convertIfNecessary(value, parameter.getParameterType(), parameter);
|
value = binder.convertIfNecessary(value, parameter.getParameterType(), parameter);
|
||||||
}
|
}
|
||||||
catch (ConversionNotSupportedException ex) {
|
catch (ConversionNotSupportedException ex) {
|
||||||
throw new ServerErrorException("Conversion not supported.", parameter, ex);
|
throw new ServerErrorException("Conversion not supported.", parameter, ex);
|
||||||
|
@ -193,7 +197,7 @@ public abstract class AbstractNamedValueArgumentResolver implements HandlerMetho
|
||||||
handleMissingValue(namedValueInfo.name, parameter, exchange);
|
handleMissingValue(namedValueInfo.name, parameter, exchange);
|
||||||
}
|
}
|
||||||
value = handleNullValue(namedValueInfo.name, value, parameter.getNestedParameterType());
|
value = handleNullValue(namedValueInfo.name, value, parameter.getNestedParameterType());
|
||||||
value = applyConversion(value, parameter, bindingContext);
|
value = applyConversion(value, namedValueInfo, parameter, bindingContext, exchange);
|
||||||
handleResolvedValue(value, namedValueInfo.name, parameter, model, exchange);
|
handleResolvedValue(value, namedValueInfo.name, parameter, model, exchange);
|
||||||
return Mono.justOrEmpty(value);
|
return Mono.justOrEmpty(value);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2016 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* 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.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.WebDataBinder;
|
||||||
|
import org.springframework.web.bind.WebExchangeDataBinder;
|
||||||
|
import org.springframework.web.bind.annotation.InitBinder;
|
||||||
|
import org.springframework.web.bind.support.WebBindingInitializer;
|
||||||
|
import org.springframework.web.method.HandlerMethod;
|
||||||
|
import org.springframework.web.reactive.HandlerResult;
|
||||||
|
import org.springframework.web.reactive.result.method.BindingContext;
|
||||||
|
import org.springframework.web.reactive.result.method.SyncInvocableHandlerMethod;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An extension of {@link BindingContext} that uses {@code @InitBinder} methods
|
||||||
|
* to initialize a data binder instance.
|
||||||
|
*
|
||||||
|
* @author Rossen Stoyanchev
|
||||||
|
* @since 5.0
|
||||||
|
*/
|
||||||
|
public class InitBinderBindingContext extends BindingContext {
|
||||||
|
|
||||||
|
private final List<SyncInvocableHandlerMethod> binderMethods;
|
||||||
|
|
||||||
|
/** BindingContext for @InitBinder method invocation */
|
||||||
|
private final BindingContext bindingContext;
|
||||||
|
|
||||||
|
|
||||||
|
public InitBinderBindingContext(WebBindingInitializer initializer,
|
||||||
|
List<SyncInvocableHandlerMethod> binderMethods) {
|
||||||
|
|
||||||
|
super(initializer);
|
||||||
|
this.binderMethods = binderMethods;
|
||||||
|
this.bindingContext = new BindingContext(initializer);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected WebExchangeDataBinder initDataBinder(WebExchangeDataBinder binder,
|
||||||
|
ServerWebExchange exchange) {
|
||||||
|
|
||||||
|
this.binderMethods.stream()
|
||||||
|
.filter(method -> isBinderMethodApplicable(method, binder))
|
||||||
|
.forEach(method -> invokeInitBinderMethod(binder, exchange, method));
|
||||||
|
|
||||||
|
return binder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the given {@code @InitBinder} method should be used to initialize
|
||||||
|
* the given WebDataBinder instance. By default we check the attributes
|
||||||
|
* names of the annotation, if present.
|
||||||
|
*/
|
||||||
|
protected boolean isBinderMethodApplicable(HandlerMethod binderMethod, WebDataBinder binder) {
|
||||||
|
InitBinder annot = binderMethod.getMethodAnnotation(InitBinder.class);
|
||||||
|
Collection<String> names = Arrays.asList(annot.value());
|
||||||
|
return (names.size() == 0 || names.contains(binder.getObjectName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void invokeInitBinderMethod(WebExchangeDataBinder binder, ServerWebExchange exchange,
|
||||||
|
SyncInvocableHandlerMethod method) {
|
||||||
|
|
||||||
|
HandlerResult result = method.invokeForHandlerResult(exchange, this.bindingContext, binder);
|
||||||
|
if (result.getReturnValue().isPresent()) {
|
||||||
|
throw new IllegalStateException("@InitBinder methods should return void: " + method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
|
@ -31,12 +32,16 @@ import org.springframework.beans.factory.BeanFactory;
|
||||||
import org.springframework.beans.factory.BeanFactoryAware;
|
import org.springframework.beans.factory.BeanFactoryAware;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||||
|
import org.springframework.core.MethodIntrospector;
|
||||||
import org.springframework.core.ReactiveAdapterRegistry;
|
import org.springframework.core.ReactiveAdapterRegistry;
|
||||||
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
import org.springframework.core.codec.ByteArrayDecoder;
|
import org.springframework.core.codec.ByteArrayDecoder;
|
||||||
import org.springframework.core.codec.ByteBufferDecoder;
|
import org.springframework.core.codec.ByteBufferDecoder;
|
||||||
import org.springframework.core.codec.StringDecoder;
|
import org.springframework.core.codec.StringDecoder;
|
||||||
import org.springframework.http.codec.DecoderHttpMessageReader;
|
import org.springframework.http.codec.DecoderHttpMessageReader;
|
||||||
import org.springframework.http.codec.HttpMessageReader;
|
import org.springframework.http.codec.HttpMessageReader;
|
||||||
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
import org.springframework.web.bind.annotation.InitBinder;
|
||||||
import org.springframework.web.bind.support.WebBindingInitializer;
|
import org.springframework.web.bind.support.WebBindingInitializer;
|
||||||
import org.springframework.web.method.HandlerMethod;
|
import org.springframework.web.method.HandlerMethod;
|
||||||
import org.springframework.web.method.annotation.ExceptionHandlerMethodResolver;
|
import org.springframework.web.method.annotation.ExceptionHandlerMethodResolver;
|
||||||
|
@ -45,6 +50,8 @@ import org.springframework.web.reactive.HandlerResult;
|
||||||
import org.springframework.web.reactive.result.method.BindingContext;
|
import org.springframework.web.reactive.result.method.BindingContext;
|
||||||
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
|
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
|
||||||
import org.springframework.web.reactive.result.method.InvocableHandlerMethod;
|
import org.springframework.web.reactive.result.method.InvocableHandlerMethod;
|
||||||
|
import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver;
|
||||||
|
import org.springframework.web.reactive.result.method.SyncInvocableHandlerMethod;
|
||||||
import org.springframework.web.server.ServerWebExchange;
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,6 +64,7 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory
|
||||||
|
|
||||||
private static final Log logger = LogFactory.getLog(RequestMappingHandlerAdapter.class);
|
private static final Log logger = LogFactory.getLog(RequestMappingHandlerAdapter.class);
|
||||||
|
|
||||||
|
|
||||||
private final List<HttpMessageReader<?>> messageReaders = new ArrayList<>(10);
|
private final List<HttpMessageReader<?>> messageReaders = new ArrayList<>(10);
|
||||||
|
|
||||||
private WebBindingInitializer webBindingInitializer;
|
private WebBindingInitializer webBindingInitializer;
|
||||||
|
@ -67,8 +75,15 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory
|
||||||
|
|
||||||
private List<HandlerMethodArgumentResolver> argumentResolvers;
|
private List<HandlerMethodArgumentResolver> argumentResolvers;
|
||||||
|
|
||||||
|
private List<SyncHandlerMethodArgumentResolver> customInitBinderArgumentResolvers;
|
||||||
|
|
||||||
|
private List<SyncHandlerMethodArgumentResolver> initBinderArgumentResolvers;
|
||||||
|
|
||||||
private ConfigurableBeanFactory beanFactory;
|
private ConfigurableBeanFactory beanFactory;
|
||||||
|
|
||||||
|
|
||||||
|
private final Map<Class<?>, Set<Method>> initBinderCache = new ConcurrentHashMap<>(64);
|
||||||
|
|
||||||
private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache =
|
private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache =
|
||||||
new ConcurrentHashMap<>(64);
|
new ConcurrentHashMap<>(64);
|
||||||
|
|
||||||
|
@ -119,10 +134,10 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provide custom argument resolvers without overriding the built-in ones.
|
* Configure custom argument resolvers without overriding the built-in ones.
|
||||||
*/
|
*/
|
||||||
public void setCustomArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
|
public void setCustomArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
|
||||||
this.customArgumentResolvers = argumentResolvers;
|
this.customArgumentResolvers = resolvers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -147,6 +162,39 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory
|
||||||
return this.argumentResolvers;
|
return this.argumentResolvers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure custom argument resolvers for {@code @InitBinder} methods.
|
||||||
|
*/
|
||||||
|
public void setCustomInitBinderArgumentResolvers(List<SyncHandlerMethodArgumentResolver> resolvers) {
|
||||||
|
this.customInitBinderArgumentResolvers = resolvers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the custom {@code @InitBinder} argument resolvers.
|
||||||
|
*/
|
||||||
|
public List<SyncHandlerMethodArgumentResolver> getCustomInitBinderArgumentResolvers() {
|
||||||
|
return this.customInitBinderArgumentResolvers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the supported argument types in {@code @InitBinder} methods.
|
||||||
|
*/
|
||||||
|
public void setInitBinderArgumentResolvers(List<SyncHandlerMethodArgumentResolver> resolvers) {
|
||||||
|
this.initBinderArgumentResolvers = null;
|
||||||
|
if (resolvers != null) {
|
||||||
|
this.initBinderArgumentResolvers = new ArrayList<>();
|
||||||
|
this.initBinderArgumentResolvers.addAll(resolvers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the configured argument resolvers for {@code @InitBinder} methods.
|
||||||
|
*/
|
||||||
|
public List<SyncHandlerMethodArgumentResolver> getInitBinderArgumentResolvers() {
|
||||||
|
return this.initBinderArgumentResolvers;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link ConfigurableBeanFactory} is expected for resolving expressions
|
* A {@link ConfigurableBeanFactory} is expected for resolving expressions
|
||||||
* in method argument default values.
|
* in method argument default values.
|
||||||
|
@ -166,21 +214,22 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory
|
||||||
@Override
|
@Override
|
||||||
public void afterPropertiesSet() throws Exception {
|
public void afterPropertiesSet() throws Exception {
|
||||||
if (this.argumentResolvers == null) {
|
if (this.argumentResolvers == null) {
|
||||||
this.argumentResolvers = initArgumentResolvers();
|
this.argumentResolvers = getDefaultArgumentResolvers();
|
||||||
|
}
|
||||||
|
if (this.initBinderArgumentResolvers == null) {
|
||||||
|
this.initBinderArgumentResolvers = getDefaultInitBinderArgumentResolvers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<HandlerMethodArgumentResolver> initArgumentResolvers() {
|
protected List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
|
||||||
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
|
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();
|
||||||
|
|
||||||
ReactiveAdapterRegistry adapterRegistry = getReactiveAdapterRegistry();
|
|
||||||
|
|
||||||
// Annotation-based argument resolution
|
// Annotation-based argument resolution
|
||||||
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
|
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
|
||||||
resolvers.add(new RequestParamMapMethodArgumentResolver());
|
resolvers.add(new RequestParamMapMethodArgumentResolver());
|
||||||
resolvers.add(new PathVariableMethodArgumentResolver(getBeanFactory()));
|
resolvers.add(new PathVariableMethodArgumentResolver(getBeanFactory()));
|
||||||
resolvers.add(new PathVariableMapMethodArgumentResolver());
|
resolvers.add(new PathVariableMapMethodArgumentResolver());
|
||||||
resolvers.add(new RequestBodyArgumentResolver(getMessageReaders(), adapterRegistry));
|
resolvers.add(new RequestBodyArgumentResolver(getMessageReaders(), getReactiveAdapterRegistry()));
|
||||||
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
|
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
|
||||||
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
|
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
|
||||||
resolvers.add(new CookieValueMethodArgumentResolver(getBeanFactory()));
|
resolvers.add(new CookieValueMethodArgumentResolver(getBeanFactory()));
|
||||||
|
@ -189,7 +238,7 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory
|
||||||
resolvers.add(new RequestAttributeMethodArgumentResolver(getBeanFactory()));
|
resolvers.add(new RequestAttributeMethodArgumentResolver(getBeanFactory()));
|
||||||
|
|
||||||
// Type-based argument resolution
|
// Type-based argument resolution
|
||||||
resolvers.add(new HttpEntityArgumentResolver(getMessageReaders(), adapterRegistry));
|
resolvers.add(new HttpEntityArgumentResolver(getMessageReaders(), getReactiveAdapterRegistry()));
|
||||||
resolvers.add(new ModelArgumentResolver());
|
resolvers.add(new ModelArgumentResolver());
|
||||||
resolvers.add(new ServerWebExchangeArgumentResolver());
|
resolvers.add(new ServerWebExchangeArgumentResolver());
|
||||||
resolvers.add(new WebSessionArgumentResolver());
|
resolvers.add(new WebSessionArgumentResolver());
|
||||||
|
@ -204,6 +253,35 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory
|
||||||
return resolvers;
|
return resolvers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected List<SyncHandlerMethodArgumentResolver> getDefaultInitBinderArgumentResolvers() {
|
||||||
|
List<SyncHandlerMethodArgumentResolver> resolvers = new ArrayList<>();
|
||||||
|
|
||||||
|
// Annotation-based argument resolution
|
||||||
|
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
|
||||||
|
resolvers.add(new RequestParamMapMethodArgumentResolver());
|
||||||
|
resolvers.add(new PathVariableMethodArgumentResolver(getBeanFactory()));
|
||||||
|
resolvers.add(new PathVariableMapMethodArgumentResolver());
|
||||||
|
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
|
||||||
|
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
|
||||||
|
resolvers.add(new CookieValueMethodArgumentResolver(getBeanFactory()));
|
||||||
|
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
|
||||||
|
resolvers.add(new RequestAttributeMethodArgumentResolver(getBeanFactory()));
|
||||||
|
|
||||||
|
// Type-based argument resolution
|
||||||
|
resolvers.add(new ModelArgumentResolver());
|
||||||
|
resolvers.add(new ServerWebExchangeArgumentResolver());
|
||||||
|
|
||||||
|
// Custom resolvers
|
||||||
|
if (getCustomArgumentResolvers() != null) {
|
||||||
|
resolvers.addAll(getCustomInitBinderArgumentResolvers());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Catch-all
|
||||||
|
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
|
||||||
|
return resolvers;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supports(Object handler) {
|
public boolean supports(Object handler) {
|
||||||
return HandlerMethod.class.equals(handler.getClass());
|
return HandlerMethod.class.equals(handler.getClass());
|
||||||
|
@ -213,8 +291,8 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory
|
||||||
public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
|
public Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {
|
||||||
HandlerMethod handlerMethod = (HandlerMethod) handler;
|
HandlerMethod handlerMethod = (HandlerMethod) handler;
|
||||||
InvocableHandlerMethod invocable = new InvocableHandlerMethod(handlerMethod);
|
InvocableHandlerMethod invocable = new InvocableHandlerMethod(handlerMethod);
|
||||||
invocable.setHandlerMethodArgumentResolvers(getArgumentResolvers());
|
invocable.setArgumentResolvers(getArgumentResolvers());
|
||||||
BindingContext bindingContext = new BindingContext(getWebBindingInitializer());
|
BindingContext bindingContext = getBindingContext(handlerMethod);
|
||||||
return invocable.invoke(exchange, bindingContext)
|
return invocable.invoke(exchange, bindingContext)
|
||||||
.map(result -> result.setExceptionHandler(
|
.map(result -> result.setExceptionHandler(
|
||||||
ex -> handleException(ex, handlerMethod, bindingContext, exchange)))
|
ex -> handleException(ex, handlerMethod, bindingContext, exchange)))
|
||||||
|
@ -222,6 +300,24 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory
|
||||||
ex, handlerMethod, bindingContext, exchange));
|
ex, handlerMethod, bindingContext, exchange));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BindingContext getBindingContext(HandlerMethod handlerMethod) {
|
||||||
|
Class<?> handlerType = handlerMethod.getBeanType();
|
||||||
|
Set<Method> methods = this.initBinderCache.get(handlerType);
|
||||||
|
if (methods == null) {
|
||||||
|
methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
|
||||||
|
this.initBinderCache.put(handlerType, methods);
|
||||||
|
}
|
||||||
|
List<SyncInvocableHandlerMethod> initBinderMethods = new ArrayList<>();
|
||||||
|
for (Method method : methods) {
|
||||||
|
Object bean = handlerMethod.getBean();
|
||||||
|
SyncInvocableHandlerMethod initBinderMethod = new SyncInvocableHandlerMethod(bean, method);
|
||||||
|
initBinderMethod.setArgumentResolvers(new ArrayList<>(this.initBinderArgumentResolvers));
|
||||||
|
initBinderMethods.add(initBinderMethod);
|
||||||
|
}
|
||||||
|
return new InitBinderBindingContext(getWebBindingInitializer(), initBinderMethods);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private Mono<HandlerResult> handleException(Throwable ex, HandlerMethod handlerMethod,
|
private Mono<HandlerResult> handleException(Throwable ex, HandlerMethod handlerMethod,
|
||||||
BindingContext bindingContext, ServerWebExchange exchange) {
|
BindingContext bindingContext, ServerWebExchange exchange) {
|
||||||
|
|
||||||
|
@ -231,7 +327,7 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("Invoking @ExceptionHandler method: " + invocable.getMethod());
|
logger.debug("Invoking @ExceptionHandler method: " + invocable.getMethod());
|
||||||
}
|
}
|
||||||
invocable.setHandlerMethodArgumentResolvers(getArgumentResolvers());
|
invocable.setArgumentResolvers(getArgumentResolvers());
|
||||||
bindingContext.getModel().clear();
|
bindingContext.getModel().clear();
|
||||||
return invocable.invoke(exchange, bindingContext, ex);
|
return invocable.invoke(exchange, bindingContext, ex);
|
||||||
}
|
}
|
||||||
|
@ -259,4 +355,11 @@ public class RequestMappingHandlerAdapter implements HandlerAdapter, BeanFactory
|
||||||
return (method != null ? new InvocableHandlerMethod(handlerMethod.getBean(), method) : null);
|
return (method != null ? new InvocableHandlerMethod(handlerMethod.getBean(), method) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MethodFilter that matches {@link InitBinder @InitBinder} methods.
|
||||||
|
*/
|
||||||
|
public static final ReflectionUtils.MethodFilter INIT_BINDER_METHODS =
|
||||||
|
method -> AnnotationUtils.findAnnotation(method, InitBinder.class) != null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,7 +154,7 @@ public class InvocableHandlerMethodTests {
|
||||||
HandlerMethodArgumentResolver resolver = mock(HandlerMethodArgumentResolver.class);
|
HandlerMethodArgumentResolver resolver = mock(HandlerMethodArgumentResolver.class);
|
||||||
when(resolver.supportsParameter(any())).thenReturn(true);
|
when(resolver.supportsParameter(any())).thenReturn(true);
|
||||||
when(resolver.resolveArgument(any(), any(), any())).thenReturn(resolvedValue);
|
when(resolver.resolveArgument(any(), any(), any())).thenReturn(resolvedValue);
|
||||||
handlerMethod.setHandlerMethodArgumentResolvers(Collections.singletonList(resolver));
|
handlerMethod.setArgumentResolvers(Collections.singletonList(resolver));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertHandlerResultValue(Mono<HandlerResult> mono, String expected) {
|
private void assertHandlerResultValue(Mono<HandlerResult> mono, String expected) {
|
||||||
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2016 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.web.reactive.result.method.annotation;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||||
|
import org.springframework.core.convert.ConversionService;
|
||||||
|
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||||
|
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||||
|
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||||
|
import org.springframework.web.bind.WebDataBinder;
|
||||||
|
import org.springframework.web.bind.annotation.InitBinder;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
|
||||||
|
import org.springframework.web.reactive.result.method.BindingContext;
|
||||||
|
import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver;
|
||||||
|
import org.springframework.web.reactive.result.method.SyncInvocableHandlerMethod;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||||
|
import org.springframework.web.server.session.DefaultWebSessionManager;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for {@link InitBinderBindingContext}.
|
||||||
|
* @author Rossen Stoyanchev
|
||||||
|
*/
|
||||||
|
public class InitBinderBindingContextTests {
|
||||||
|
|
||||||
|
private final ConfigurableWebBindingInitializer bindingInitializer = new ConfigurableWebBindingInitializer();
|
||||||
|
|
||||||
|
private final List<SyncHandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();
|
||||||
|
|
||||||
|
private final ServerWebExchange exchange = new DefaultServerWebExchange(
|
||||||
|
new MockServerHttpRequest(), new MockServerHttpResponse(), new DefaultWebSessionManager());
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createBinder() throws Exception {
|
||||||
|
BindingContext context = createBindingContext("initBinder", WebDataBinder.class);
|
||||||
|
WebDataBinder dataBinder = context.createDataBinder(this.exchange, null, null);
|
||||||
|
|
||||||
|
assertNotNull(dataBinder.getDisallowedFields());
|
||||||
|
assertEquals("id", dataBinder.getDisallowedFields()[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createBinderWithGlobalInitialization() throws Exception {
|
||||||
|
ConversionService conversionService = new DefaultFormattingConversionService();
|
||||||
|
bindingInitializer.setConversionService(conversionService);
|
||||||
|
|
||||||
|
BindingContext context = createBindingContext("initBinder", WebDataBinder.class);
|
||||||
|
WebDataBinder dataBinder = context.createDataBinder(this.exchange, null, null);
|
||||||
|
|
||||||
|
assertSame(conversionService, dataBinder.getConversionService());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createBinderWithAttrName() throws Exception {
|
||||||
|
BindingContext context = createBindingContext("initBinderWithAttributeName", WebDataBinder.class);
|
||||||
|
WebDataBinder dataBinder = context.createDataBinder(this.exchange, null, "foo");
|
||||||
|
|
||||||
|
assertNotNull(dataBinder.getDisallowedFields());
|
||||||
|
assertEquals("id", dataBinder.getDisallowedFields()[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createBinderWithAttrNameNoMatch() throws Exception {
|
||||||
|
BindingContext context = createBindingContext("initBinderWithAttributeName", WebDataBinder.class);
|
||||||
|
WebDataBinder dataBinder = context.createDataBinder(this.exchange, null, "invalidName");
|
||||||
|
|
||||||
|
assertNull(dataBinder.getDisallowedFields());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createBinderNullAttrName() throws Exception {
|
||||||
|
BindingContext context = createBindingContext("initBinderWithAttributeName", WebDataBinder.class);
|
||||||
|
WebDataBinder dataBinder = context.createDataBinder(this.exchange, null, null);
|
||||||
|
|
||||||
|
assertNull(dataBinder.getDisallowedFields());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalStateException.class)
|
||||||
|
public void returnValueNotExpected() throws Exception {
|
||||||
|
BindingContext context = createBindingContext("initBinderReturnValue", WebDataBinder.class);
|
||||||
|
context.createDataBinder(this.exchange, null, "invalidName");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createBinderTypeConversion() throws Exception {
|
||||||
|
this.exchange.getRequest().getQueryParams().add("requestParam", "22");
|
||||||
|
this.argumentResolvers.add(new RequestParamMethodArgumentResolver(null, false));
|
||||||
|
|
||||||
|
BindingContext context = createBindingContext("initBinderTypeConversion", WebDataBinder.class, int.class);
|
||||||
|
WebDataBinder dataBinder = context.createDataBinder(this.exchange, null, "foo");
|
||||||
|
|
||||||
|
assertNotNull(dataBinder.getDisallowedFields());
|
||||||
|
assertEquals("requestParam-22", dataBinder.getDisallowedFields()[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private BindingContext createBindingContext(String methodName, Class<?>... parameterTypes)
|
||||||
|
throws Exception {
|
||||||
|
|
||||||
|
Object handler = new InitBinderHandler();
|
||||||
|
Method method = handler.getClass().getMethod(methodName, parameterTypes);
|
||||||
|
|
||||||
|
SyncInvocableHandlerMethod handlerMethod = new SyncInvocableHandlerMethod(handler, method);
|
||||||
|
handlerMethod.setArgumentResolvers(new ArrayList<>(this.argumentResolvers));
|
||||||
|
handlerMethod.setParameterNameDiscoverer(new LocalVariableTableParameterNameDiscoverer());
|
||||||
|
|
||||||
|
return new InitBinderBindingContext(
|
||||||
|
this.bindingInitializer, Collections.singletonList(handlerMethod));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static class InitBinderHandler {
|
||||||
|
|
||||||
|
@InitBinder
|
||||||
|
public void initBinder(WebDataBinder dataBinder) {
|
||||||
|
dataBinder.setDisallowedFields("id");
|
||||||
|
}
|
||||||
|
|
||||||
|
@InitBinder(value="foo")
|
||||||
|
public void initBinderWithAttributeName(WebDataBinder dataBinder) {
|
||||||
|
dataBinder.setDisallowedFields("id");
|
||||||
|
}
|
||||||
|
|
||||||
|
@InitBinder
|
||||||
|
public String initBinderReturnValue(WebDataBinder dataBinder) {
|
||||||
|
return "invalid";
|
||||||
|
}
|
||||||
|
|
||||||
|
@InitBinder
|
||||||
|
public void initBinderTypeConversion(WebDataBinder dataBinder, @RequestParam int requestParam) {
|
||||||
|
dataBinder.setDisallowedFields("requestParam-" + requestParam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2016 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.springframework.web.reactive.result.method.annotation;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.beans.propertyeditors.CustomDateEditor;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.WebDataBinder;
|
||||||
|
import org.springframework.web.bind.annotation.InitBinder;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
import org.springframework.web.reactive.config.EnableWebReactive;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data binding and type conversion related integration tests for
|
||||||
|
* {@code @Controller}-annotated classes.
|
||||||
|
*
|
||||||
|
* @author Rossen Stoyanchev
|
||||||
|
*/
|
||||||
|
public class RequestMappingDataBindingIntegrationTests extends AbstractRequestMappingIntegrationTests {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ApplicationContext initApplicationContext() {
|
||||||
|
AnnotationConfigApplicationContext wac = new AnnotationConfigApplicationContext();
|
||||||
|
wac.register(WebConfig.class);
|
||||||
|
wac.refresh();
|
||||||
|
return wac;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void handleDateParam() throws Exception {
|
||||||
|
assertEquals("Processed date!",
|
||||||
|
performPost("/date-param?date=2016-10-31&date-pattern=YYYY-mm-dd",
|
||||||
|
new HttpHeaders(), null, String.class).getBody());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebReactive
|
||||||
|
@ComponentScan(resourcePattern = "**/RequestMappingDataBindingIntegrationTests*.class")
|
||||||
|
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||||
|
static class WebConfig {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static class TestController {
|
||||||
|
|
||||||
|
@InitBinder
|
||||||
|
public void initBinder(WebDataBinder dataBinder, @RequestParam("date-pattern") String pattern) {
|
||||||
|
CustomDateEditor dateEditor = new CustomDateEditor(new SimpleDateFormat(pattern), false);
|
||||||
|
dataBinder.registerCustomEditor(Date.class, dateEditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/date-param")
|
||||||
|
@ResponseBody
|
||||||
|
public String handleDateParam(@RequestParam Date date) {
|
||||||
|
return "Processed date!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -39,12 +39,15 @@ public class InitBinderDataBinderFactory extends DefaultDataBinderFactory {
|
||||||
|
|
||||||
private final List<InvocableHandlerMethod> binderMethods;
|
private final List<InvocableHandlerMethod> binderMethods;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new instance.
|
* Create a new instance.
|
||||||
* @param binderMethods {@code @InitBinder} methods, or {@code null}
|
* @param binderMethods {@code @InitBinder} methods, or {@code null}
|
||||||
* @param initializer for global data binder intialization
|
* @param initializer for global data binder intialization
|
||||||
*/
|
*/
|
||||||
public InitBinderDataBinderFactory(List<InvocableHandlerMethod> binderMethods, WebBindingInitializer initializer) {
|
public InitBinderDataBinderFactory(List<InvocableHandlerMethod> binderMethods,
|
||||||
|
WebBindingInitializer initializer) {
|
||||||
|
|
||||||
super(initializer);
|
super(initializer);
|
||||||
this.binderMethods = (binderMethods != null) ? binderMethods : new ArrayList<>();
|
this.binderMethods = (binderMethods != null) ? binderMethods : new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
@ -61,20 +64,20 @@ public class InitBinderDataBinderFactory extends DefaultDataBinderFactory {
|
||||||
if (isBinderMethodApplicable(binderMethod, binder)) {
|
if (isBinderMethodApplicable(binderMethod, binder)) {
|
||||||
Object returnValue = binderMethod.invokeForRequest(request, null, binder);
|
Object returnValue = binderMethod.invokeForRequest(request, null, binder);
|
||||||
if (returnValue != null) {
|
if (returnValue != null) {
|
||||||
throw new IllegalStateException("@InitBinder methods should return void: " + binderMethod);
|
throw new IllegalStateException(
|
||||||
|
"@InitBinder methods should return void: " + binderMethod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return {@code true} if the given {@code @InitBinder} method should be
|
* Whether the given {@code @InitBinder} method should be used to initialize
|
||||||
* invoked to initialize the given WebDataBinder.
|
* the given WebDataBinder instance. By default we check the attributes
|
||||||
* <p>The default implementation checks if target object name is included
|
* names of the annotation, if present.
|
||||||
* in the attribute names specified in the {@code @InitBinder} annotation.
|
|
||||||
*/
|
*/
|
||||||
protected boolean isBinderMethodApplicable(HandlerMethod initBinderMethod, WebDataBinder binder) {
|
protected boolean isBinderMethodApplicable(HandlerMethod binderMethod, WebDataBinder binder) {
|
||||||
InitBinder annot = initBinderMethod.getMethodAnnotation(InitBinder.class);
|
InitBinder annot = binderMethod.getMethodAnnotation(InitBinder.class);
|
||||||
Collection<String> names = Arrays.asList(annot.value());
|
Collection<String> names = Arrays.asList(annot.value());
|
||||||
return (names.size() == 0 || names.contains(binder.getObjectName()));
|
return (names.size() == 0 || names.contains(binder.getObjectName()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,8 @@
|
||||||
package org.springframework.web.method.annotation;
|
package org.springframework.web.method.annotation;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Arrays;
|
import java.util.Collections;
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||||
|
@ -37,7 +36,10 @@ import org.springframework.web.context.request.ServletWebRequest;
|
||||||
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
|
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
|
||||||
import org.springframework.web.method.support.InvocableHandlerMethod;
|
import org.springframework.web.method.support.InvocableHandlerMethod;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test fixture with {@link InitBinderDataBinderFactory}.
|
* Test fixture with {@link InitBinderDataBinderFactory}.
|
||||||
|
@ -46,23 +48,19 @@ import static org.junit.Assert.*;
|
||||||
*/
|
*/
|
||||||
public class InitBinderDataBinderFactoryTests {
|
public class InitBinderDataBinderFactoryTests {
|
||||||
|
|
||||||
private ConfigurableWebBindingInitializer bindingInitializer;
|
private final ConfigurableWebBindingInitializer bindingInitializer =
|
||||||
|
new ConfigurableWebBindingInitializer();
|
||||||
|
|
||||||
private HandlerMethodArgumentResolverComposite argumentResolvers;
|
private final HandlerMethodArgumentResolverComposite argumentResolvers =
|
||||||
|
new HandlerMethodArgumentResolverComposite();
|
||||||
|
|
||||||
private NativeWebRequest webRequest;
|
private final NativeWebRequest webRequest = new ServletWebRequest(new MockHttpServletRequest());
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
bindingInitializer = new ConfigurableWebBindingInitializer();
|
|
||||||
argumentResolvers = new HandlerMethodArgumentResolverComposite();
|
|
||||||
webRequest = new ServletWebRequest(new MockHttpServletRequest());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createBinder() throws Exception {
|
public void createBinder() throws Exception {
|
||||||
WebDataBinderFactory factory = createBinderFactory("initBinder", WebDataBinder.class);
|
WebDataBinderFactory factory = createFactory("initBinder", WebDataBinder.class);
|
||||||
WebDataBinder dataBinder = factory.createBinder(webRequest, null, null);
|
WebDataBinder dataBinder = factory.createBinder(this.webRequest, null, null);
|
||||||
|
|
||||||
assertNotNull(dataBinder.getDisallowedFields());
|
assertNotNull(dataBinder.getDisallowedFields());
|
||||||
assertEquals("id", dataBinder.getDisallowedFields()[0]);
|
assertEquals("id", dataBinder.getDisallowedFields()[0]);
|
||||||
|
@ -73,16 +71,16 @@ public class InitBinderDataBinderFactoryTests {
|
||||||
ConversionService conversionService = new DefaultFormattingConversionService();
|
ConversionService conversionService = new DefaultFormattingConversionService();
|
||||||
bindingInitializer.setConversionService(conversionService);
|
bindingInitializer.setConversionService(conversionService);
|
||||||
|
|
||||||
WebDataBinderFactory factory = createBinderFactory("initBinder", WebDataBinder.class);
|
WebDataBinderFactory factory = createFactory("initBinder", WebDataBinder.class);
|
||||||
WebDataBinder dataBinder = factory.createBinder(webRequest, null, null);
|
WebDataBinder dataBinder = factory.createBinder(this.webRequest, null, null);
|
||||||
|
|
||||||
assertSame(conversionService, dataBinder.getConversionService());
|
assertSame(conversionService, dataBinder.getConversionService());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createBinderWithAttrName() throws Exception {
|
public void createBinderWithAttrName() throws Exception {
|
||||||
WebDataBinderFactory factory = createBinderFactory("initBinderWithAttributeName", WebDataBinder.class);
|
WebDataBinderFactory factory = createFactory("initBinderWithAttributeName", WebDataBinder.class);
|
||||||
WebDataBinder dataBinder = factory.createBinder(webRequest, null, "foo");
|
WebDataBinder dataBinder = factory.createBinder(this.webRequest, null, "foo");
|
||||||
|
|
||||||
assertNotNull(dataBinder.getDisallowedFields());
|
assertNotNull(dataBinder.getDisallowedFields());
|
||||||
assertEquals("id", dataBinder.getDisallowedFields()[0]);
|
assertEquals("id", dataBinder.getDisallowedFields()[0]);
|
||||||
|
@ -90,52 +88,54 @@ public class InitBinderDataBinderFactoryTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createBinderWithAttrNameNoMatch() throws Exception {
|
public void createBinderWithAttrNameNoMatch() throws Exception {
|
||||||
WebDataBinderFactory factory = createBinderFactory("initBinderWithAttributeName", WebDataBinder.class);
|
WebDataBinderFactory factory = createFactory("initBinderWithAttributeName", WebDataBinder.class);
|
||||||
WebDataBinder dataBinder = factory.createBinder(webRequest, null, "invalidName");
|
WebDataBinder dataBinder = factory.createBinder(this.webRequest, null, "invalidName");
|
||||||
|
|
||||||
assertNull(dataBinder.getDisallowedFields());
|
assertNull(dataBinder.getDisallowedFields());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createBinderNullAttrName() throws Exception {
|
public void createBinderNullAttrName() throws Exception {
|
||||||
WebDataBinderFactory factory = createBinderFactory("initBinderWithAttributeName", WebDataBinder.class);
|
WebDataBinderFactory factory = createFactory("initBinderWithAttributeName", WebDataBinder.class);
|
||||||
WebDataBinder dataBinder = factory.createBinder(webRequest, null, null);
|
WebDataBinder dataBinder = factory.createBinder(this.webRequest, null, null);
|
||||||
|
|
||||||
assertNull(dataBinder.getDisallowedFields());
|
assertNull(dataBinder.getDisallowedFields());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalStateException.class)
|
@Test(expected = IllegalStateException.class)
|
||||||
public void returnValueNotExpected() throws Exception {
|
public void returnValueNotExpected() throws Exception {
|
||||||
WebDataBinderFactory factory = createBinderFactory("initBinderReturnValue", WebDataBinder.class);
|
WebDataBinderFactory factory = createFactory("initBinderReturnValue", WebDataBinder.class);
|
||||||
factory.createBinder(webRequest, null, "invalidName");
|
factory.createBinder(this.webRequest, null, "invalidName");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createBinderTypeConversion() throws Exception {
|
public void createBinderTypeConversion() throws Exception {
|
||||||
webRequest.getNativeRequest(MockHttpServletRequest.class).setParameter("requestParam", "22");
|
this.webRequest.getNativeRequest(MockHttpServletRequest.class).setParameter("requestParam", "22");
|
||||||
argumentResolvers.addResolver(new RequestParamMethodArgumentResolver(null, false));
|
this.argumentResolvers.addResolver(new RequestParamMethodArgumentResolver(null, false));
|
||||||
|
|
||||||
WebDataBinderFactory factory = createBinderFactory("initBinderTypeConversion", WebDataBinder.class, int.class);
|
WebDataBinderFactory factory = createFactory("initBinderTypeConversion", WebDataBinder.class, int.class);
|
||||||
WebDataBinder dataBinder = factory.createBinder(webRequest, null, "foo");
|
WebDataBinder dataBinder = factory.createBinder(this.webRequest, null, "foo");
|
||||||
|
|
||||||
assertNotNull(dataBinder.getDisallowedFields());
|
assertNotNull(dataBinder.getDisallowedFields());
|
||||||
assertEquals("requestParam-22", dataBinder.getDisallowedFields()[0]);
|
assertEquals("requestParam-22", dataBinder.getDisallowedFields()[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private WebDataBinderFactory createBinderFactory(String methodName, Class<?>... parameterTypes)
|
private WebDataBinderFactory createFactory(String methodName, Class<?>... parameterTypes)
|
||||||
throws Exception {
|
throws Exception {
|
||||||
|
|
||||||
Object handler = new InitBinderHandler();
|
Object handler = new InitBinderHandler();
|
||||||
Method method = handler.getClass().getMethod(methodName, parameterTypes);
|
Method method = handler.getClass().getMethod(methodName, parameterTypes);
|
||||||
|
|
||||||
InvocableHandlerMethod handlerMethod = new InvocableHandlerMethod(handler, method);
|
InvocableHandlerMethod handlerMethod = new InvocableHandlerMethod(handler, method);
|
||||||
handlerMethod.setHandlerMethodArgumentResolvers(argumentResolvers);
|
handlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
|
||||||
handlerMethod.setDataBinderFactory(new DefaultDataBinderFactory(null));
|
handlerMethod.setDataBinderFactory(new DefaultDataBinderFactory(null));
|
||||||
handlerMethod.setParameterNameDiscoverer(new LocalVariableTableParameterNameDiscoverer());
|
handlerMethod.setParameterNameDiscoverer(new LocalVariableTableParameterNameDiscoverer());
|
||||||
|
|
||||||
return new InitBinderDataBinderFactory(Arrays.asList(handlerMethod), bindingInitializer);
|
return new InitBinderDataBinderFactory(
|
||||||
|
Collections.singletonList(handlerMethod), this.bindingInitializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class InitBinderHandler {
|
private static class InitBinderHandler {
|
||||||
|
|
||||||
@InitBinder
|
@InitBinder
|
||||||
|
|
Loading…
Reference in New Issue