POLISH CREATION OF DATA BINDERS FOR @RequestMapping METHODS
Make it possible to hook in custom ServletRequestDataBinderFactory by overriding RequestMappingHandlerAdapter. Create ExtendedServletRequestDataBinder to add URI template vars to the binding values taking advantage of a new extension hook in ServletRequestDataBinder to provide additional values to bind.
This commit is contained in:
parent
6bc4ea058c
commit
48f7dcc464
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2011 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.servlet.mvc.method.annotation;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
|
||||||
|
import org.springframework.beans.MutablePropertyValues;
|
||||||
|
import org.springframework.web.bind.ServletRequestDataBinder;
|
||||||
|
import org.springframework.web.servlet.HandlerMapping;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subclass of {@link ServletRequestDataBinder} that adds URI template variables
|
||||||
|
* to the values used for data binding.
|
||||||
|
*
|
||||||
|
* @author Rossen Stoyanchev
|
||||||
|
* @since 3.1
|
||||||
|
*/
|
||||||
|
public class ExtendedServletRequestDataBinder extends ServletRequestDataBinder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance, with default object name.
|
||||||
|
* @param target the target object to bind onto (or <code>null</code>
|
||||||
|
* if the binder is just used to convert a plain parameter value)
|
||||||
|
* @see #DEFAULT_OBJECT_NAME
|
||||||
|
*/
|
||||||
|
public ExtendedServletRequestDataBinder(Object target) {
|
||||||
|
super(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance.
|
||||||
|
* @param target the target object to bind onto (or <code>null</code>
|
||||||
|
* if the binder is just used to convert a plain parameter value)
|
||||||
|
* @param objectName the name of the target object
|
||||||
|
* @see #DEFAULT_OBJECT_NAME
|
||||||
|
*/
|
||||||
|
public ExtendedServletRequestDataBinder(Object target, String objectName) {
|
||||||
|
super(target, objectName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add URI template variables to the property values used for data binding.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
|
||||||
|
String attr = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
|
||||||
|
mpvs.addPropertyValues((Map<String, String>) request.getAttribute(attr));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -676,7 +676,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||||
return modelFactory;
|
return modelFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) {
|
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
|
||||||
Class<?> handlerType = handlerMethod.getBeanType();
|
Class<?> handlerType = handlerMethod.getBeanType();
|
||||||
WebDataBinderFactory binderFactory = this.dataBinderFactoryCache.get(handlerType);
|
WebDataBinderFactory binderFactory = this.dataBinderFactoryCache.get(handlerType);
|
||||||
if (binderFactory == null) {
|
if (binderFactory == null) {
|
||||||
|
|
@ -688,12 +688,25 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
|
||||||
binderMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
|
binderMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
|
||||||
binderMethods.add(binderMethod);
|
binderMethods.add(binderMethod);
|
||||||
}
|
}
|
||||||
binderFactory = new ServletRequestDataBinderFactory(binderMethods, this.webBindingInitializer);
|
binderFactory = createDataBinderFactory(binderMethods);
|
||||||
this.dataBinderFactoryCache.put(handlerType, binderFactory);
|
this.dataBinderFactoryCache.put(handlerType, binderFactory);
|
||||||
}
|
}
|
||||||
return binderFactory;
|
return binderFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Template method to create a new ServletRequestDataBinderFactory instance.
|
||||||
|
* <p>The default implementation creates a ServletRequestDataBinderFactory.
|
||||||
|
* This can be overridden for custom ServletRequestDataBinder subclasses.
|
||||||
|
* @param binderMethods {@code @InitBinder} methods
|
||||||
|
* @return the ServletRequestDataBinderFactory instance to use
|
||||||
|
* @throws Exception in case of invalid state or arguments
|
||||||
|
*/
|
||||||
|
protected ServletRequestDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods)
|
||||||
|
throws Exception {
|
||||||
|
return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* MethodFilter that matches {@link InitBinder @InitBinder} methods.
|
* MethodFilter that matches {@link InitBinder @InitBinder} methods.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -17,21 +17,15 @@
|
||||||
package org.springframework.web.servlet.mvc.method.annotation;
|
package org.springframework.web.servlet.mvc.method.annotation;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.springframework.beans.MutablePropertyValues;
|
|
||||||
import org.springframework.web.bind.ServletRequestDataBinder;
|
import org.springframework.web.bind.ServletRequestDataBinder;
|
||||||
import org.springframework.web.bind.WebDataBinder;
|
|
||||||
import org.springframework.web.bind.support.WebBindingInitializer;
|
import org.springframework.web.bind.support.WebBindingInitializer;
|
||||||
import org.springframework.web.context.request.NativeWebRequest;
|
import org.springframework.web.context.request.NativeWebRequest;
|
||||||
import org.springframework.web.context.request.RequestAttributes;
|
|
||||||
import org.springframework.web.method.annotation.InitBinderDataBinderFactory;
|
import org.springframework.web.method.annotation.InitBinderDataBinderFactory;
|
||||||
import org.springframework.web.method.support.InvocableHandlerMethod;
|
import org.springframework.web.method.support.InvocableHandlerMethod;
|
||||||
import org.springframework.web.servlet.HandlerMapping;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a WebDataBinder of type {@link ServletRequestDataBinder} that can
|
* Creates a {@code ServletRequestDataBinder}.
|
||||||
* also use URI template variables values for data binding purposes.
|
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
* @since 3.1
|
* @since 3.1
|
||||||
|
|
@ -43,45 +37,16 @@ public class ServletRequestDataBinderFactory extends InitBinderDataBinderFactory
|
||||||
* @param binderMethods one or more {@code @InitBinder} methods
|
* @param binderMethods one or more {@code @InitBinder} methods
|
||||||
* @param initializer provides global data binder initialization
|
* @param initializer provides global data binder initialization
|
||||||
*/
|
*/
|
||||||
public ServletRequestDataBinderFactory(List<InvocableHandlerMethod> binderMethods,
|
public ServletRequestDataBinderFactory(List<InvocableHandlerMethod> binderMethods, WebBindingInitializer initializer) {
|
||||||
WebBindingInitializer initializer) {
|
|
||||||
super(binderMethods, initializer);
|
super(binderMethods, initializer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a WebDataBinder of type {@link ServletRequestDataBinder} that can
|
* Returns an instance of {@link ExtendedServletRequestDataBinder}.
|
||||||
* also use URI template variables values for data binding purposes.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected WebDataBinder createBinderInstance(Object target, String objectName, final NativeWebRequest request) {
|
protected ServletRequestDataBinder createBinderInstance(Object target, String objectName, NativeWebRequest request) {
|
||||||
return new ServletRequestDataBinder(target, objectName) {
|
return new ExtendedServletRequestDataBinder(target, objectName);
|
||||||
@Override
|
|
||||||
protected void doBind(MutablePropertyValues mpvs) {
|
|
||||||
mergeUriTemplateVariables(mpvs, request);
|
|
||||||
super.doBind(mpvs);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merge URI variable values into the given PropertyValues.
|
|
||||||
* @param mpvs the PropertyValues to add to
|
|
||||||
* @param request the current request
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected final void mergeUriTemplateVariables(MutablePropertyValues mpvs, NativeWebRequest request) {
|
|
||||||
|
|
||||||
Map<String, String> uriTemplateVars =
|
|
||||||
(Map<String, String>) request.getAttribute(
|
|
||||||
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
|
|
||||||
|
|
||||||
if (uriTemplateVars != null){
|
|
||||||
for (String variableName : uriTemplateVars.keySet()) {
|
|
||||||
if (!mpvs.contains(variableName)) {
|
|
||||||
mpvs.addPropertyValue(variableName, uriTemplateVars.get(variableName));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,42 +21,26 @@ import static org.junit.Assert.assertEquals;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.beans.TestBean;
|
import org.springframework.beans.TestBean;
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
import org.springframework.web.bind.ServletRequestDataBinder;
|
import org.springframework.web.bind.ServletRequestDataBinder;
|
||||||
import org.springframework.web.bind.WebDataBinder;
|
import org.springframework.web.bind.WebDataBinder;
|
||||||
import org.springframework.web.context.request.NativeWebRequest;
|
|
||||||
import org.springframework.web.context.request.RequestContextHolder;
|
|
||||||
import org.springframework.web.context.request.ServletWebRequest;
|
|
||||||
import org.springframework.web.servlet.HandlerMapping;
|
import org.springframework.web.servlet.HandlerMapping;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test fixture with {@link ServletRequestDataBinderFactory}.
|
* Test fixture for {@link ExtendedServletRequestDataBinder}.
|
||||||
*
|
*
|
||||||
* @author Rossen Stoyanchev
|
* @author Rossen Stoyanchev
|
||||||
*/
|
*/
|
||||||
public class ServletRequestDataBinderFactoryTests {
|
public class ExtendedServletRequestDataBinderTests {
|
||||||
|
|
||||||
private ServletRequestDataBinderFactory binderFactory;
|
|
||||||
|
|
||||||
private MockHttpServletRequest request;
|
private MockHttpServletRequest request;
|
||||||
|
|
||||||
private NativeWebRequest webRequest;
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() {
|
public void setup() {
|
||||||
binderFactory = new ServletRequestDataBinderFactory(null, null);
|
this.request = new MockHttpServletRequest();
|
||||||
request = new MockHttpServletRequest();
|
|
||||||
webRequest = new ServletWebRequest(request);
|
|
||||||
RequestContextHolder.setRequestAttributes(webRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void teardown() {
|
|
||||||
RequestContextHolder.resetRequestAttributes();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -67,7 +51,7 @@ public class ServletRequestDataBinderFactoryTests {
|
||||||
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
|
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
|
||||||
|
|
||||||
TestBean target = new TestBean();
|
TestBean target = new TestBean();
|
||||||
WebDataBinder binder = binderFactory.createBinder(webRequest, target, "");
|
WebDataBinder binder = new ExtendedServletRequestDataBinder(target, "");
|
||||||
((ServletRequestDataBinder) binder).bind(request);
|
((ServletRequestDataBinder) binder).bind(request);
|
||||||
|
|
||||||
assertEquals("nameValue", target.getName());
|
assertEquals("nameValue", target.getName());
|
||||||
|
|
@ -75,7 +59,7 @@ public class ServletRequestDataBinderFactoryTests {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void requestParamsOverrideUriTemplateVars() throws Exception {
|
public void uriTemplateVarAndRequestParam() throws Exception {
|
||||||
request.addParameter("age", "35");
|
request.addParameter("age", "35");
|
||||||
|
|
||||||
Map<String, String> uriTemplateVars = new HashMap<String, String>();
|
Map<String, String> uriTemplateVars = new HashMap<String, String>();
|
||||||
|
|
@ -84,17 +68,17 @@ public class ServletRequestDataBinderFactoryTests {
|
||||||
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
|
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
|
||||||
|
|
||||||
TestBean target = new TestBean();
|
TestBean target = new TestBean();
|
||||||
WebDataBinder binder = binderFactory.createBinder(webRequest, target, "");
|
WebDataBinder binder = new ExtendedServletRequestDataBinder(target, "");
|
||||||
((ServletRequestDataBinder) binder).bind(request);
|
((ServletRequestDataBinder) binder).bind(request);
|
||||||
|
|
||||||
assertEquals("nameValue", target.getName());
|
assertEquals("nameValue", target.getName());
|
||||||
assertEquals(35, target.getAge());
|
assertEquals(25, target.getAge());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void noUriTemplateVars() throws Exception {
|
public void noUriTemplateVars() throws Exception {
|
||||||
TestBean target = new TestBean();
|
TestBean target = new TestBean();
|
||||||
WebDataBinder binder = binderFactory.createBinder(webRequest, target, "");
|
WebDataBinder binder = new ExtendedServletRequestDataBinder(target, "");
|
||||||
((ServletRequestDataBinder) binder).bind(request);
|
((ServletRequestDataBinder) binder).bind(request);
|
||||||
|
|
||||||
assertEquals(null, target.getName());
|
assertEquals(null, target.getName());
|
||||||
|
|
@ -108,9 +108,20 @@ public class ServletRequestDataBinder extends WebDataBinder {
|
||||||
if (multipartRequest != null) {
|
if (multipartRequest != null) {
|
||||||
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
|
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
|
||||||
}
|
}
|
||||||
|
addBindValues(mpvs, request);
|
||||||
doBind(mpvs);
|
doBind(mpvs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension point that subclasses can use to add extra bind values for a
|
||||||
|
* request. Invoked before {@link #doBind(MutablePropertyValues)}.
|
||||||
|
* The default implementation is empty.
|
||||||
|
* @param mpvs the property values that will be used for data binding
|
||||||
|
* @param request the current request
|
||||||
|
*/
|
||||||
|
protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Treats errors as fatal.
|
* Treats errors as fatal.
|
||||||
* <p>Use this method only if it's an error if the input isn't valid.
|
* <p>Use this method only if it's an error if the input isn't valid.
|
||||||
|
|
|
||||||
|
|
@ -41,10 +41,12 @@ public class DefaultDataBinderFactory implements WebDataBinderFactory {
|
||||||
/**
|
/**
|
||||||
* Create a new {@link WebDataBinder} for the given target object and
|
* Create a new {@link WebDataBinder} for the given target object and
|
||||||
* initialize it through a {@link WebBindingInitializer}.
|
* initialize it through a {@link WebBindingInitializer}.
|
||||||
|
* @throws Exception in case of invalid state or arguments
|
||||||
*/
|
*/
|
||||||
public final WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName) throws Exception {
|
public final WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName)
|
||||||
|
throws Exception {
|
||||||
WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
|
WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
|
||||||
if (initializer != null) {
|
if (this.initializer != null) {
|
||||||
this.initializer.initBinder(dataBinder, webRequest);
|
this.initializer.initBinder(dataBinder, webRequest);
|
||||||
}
|
}
|
||||||
initBinder(dataBinder, webRequest);
|
initBinder(dataBinder, webRequest);
|
||||||
|
|
@ -52,13 +54,15 @@ public class DefaultDataBinderFactory implements WebDataBinderFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extension point to create the WebDataBinder instance, which is
|
* Extension point to create the WebDataBinder instance.
|
||||||
* {@link WebRequestDataBinder} by default.
|
* By default this is {@code WebRequestDataBinder}.
|
||||||
* @param target the binding target or {@code null} for type conversion only
|
* @param target the binding target or {@code null} for type conversion only
|
||||||
* @param objectName the binding target object name
|
* @param objectName the binding target object name
|
||||||
* @param webRequest the current request
|
* @param webRequest the current request
|
||||||
|
* @throws Exception in case of invalid state or arguments
|
||||||
*/
|
*/
|
||||||
protected WebDataBinder createBinderInstance(Object target, String objectName, NativeWebRequest webRequest) {
|
protected WebDataBinder createBinderInstance(Object target, String objectName, NativeWebRequest webRequest)
|
||||||
|
throws Exception {
|
||||||
return new WebRequestDataBinder(target, objectName);
|
return new WebRequestDataBinder(target, objectName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,12 +58,11 @@ public class InitBinderDataBinderFactory extends DefaultDataBinderFactory {
|
||||||
@Override
|
@Override
|
||||||
public void initBinder(WebDataBinder binder, NativeWebRequest request) throws Exception {
|
public void initBinder(WebDataBinder binder, NativeWebRequest request) throws Exception {
|
||||||
for (InvocableHandlerMethod binderMethod : this.binderMethods) {
|
for (InvocableHandlerMethod binderMethod : this.binderMethods) {
|
||||||
if (!invokeInitBinderMethod(binderMethod, binder)) {
|
if (isBinderMethodApplicable(binderMethod, binder)) {
|
||||||
continue;
|
Object returnValue = binderMethod.invokeForRequest(request, null, binder);
|
||||||
}
|
if (returnValue != null) {
|
||||||
Object returnValue = binderMethod.invokeForRequest(request, null, binder);
|
throw new IllegalStateException("@InitBinder methods should return void: " + binderMethod);
|
||||||
if (returnValue != null) {
|
}
|
||||||
throw new IllegalStateException("@InitBinder methods should return void: " + binderMethod);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -74,8 +73,8 @@ public class InitBinderDataBinderFactory extends DefaultDataBinderFactory {
|
||||||
* <p>The default implementation checks if target object name is included
|
* <p>The default implementation checks if target object name is included
|
||||||
* in the attribute names specified in the {@code @InitBinder} annotation.
|
* in the attribute names specified in the {@code @InitBinder} annotation.
|
||||||
*/
|
*/
|
||||||
protected boolean invokeInitBinderMethod(HandlerMethod binderMethod, WebDataBinder binder) {
|
protected boolean isBinderMethodApplicable(HandlerMethod initBinderMethod, WebDataBinder binder) {
|
||||||
InitBinder annot = binderMethod.getMethodAnnotation(InitBinder.class);
|
InitBinder annot = initBinderMethod.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()));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue