Browse Source

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.
pull/7/head
Rossen Stoyanchev 15 years ago
parent
commit
48f7dcc464
  1. 67
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExtendedServletRequestDataBinder.java
  2. 17
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java
  3. 47
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestDataBinderFactory.java
  4. 32
      org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExtendedServletRequestDataBinderTests.java
  5. 11
      org.springframework.web/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java
  6. 14
      org.springframework.web/src/main/java/org/springframework/web/bind/support/DefaultDataBinderFactory.java
  7. 15
      org.springframework.web/src/main/java/org/springframework/web/method/annotation/InitBinderDataBinderFactory.java

67
org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExtendedServletRequestDataBinder.java

@ -0,0 +1,67 @@ @@ -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));
}
}

17
org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java

@ -676,7 +676,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i @@ -676,7 +676,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
return modelFactory;
}
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) {
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
Class<?> handlerType = handlerMethod.getBeanType();
WebDataBinderFactory binderFactory = this.dataBinderFactoryCache.get(handlerType);
if (binderFactory == null) {
@ -688,12 +688,25 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i @@ -688,12 +688,25 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter i
binderMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
binderMethods.add(binderMethod);
}
binderFactory = new ServletRequestDataBinderFactory(binderMethods, this.webBindingInitializer);
binderFactory = createDataBinderFactory(binderMethods);
this.dataBinderFactoryCache.put(handlerType, 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.
*/

47
org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestDataBinderFactory.java

@ -17,21 +17,15 @@ @@ -17,21 +17,15 @@
package org.springframework.web.servlet.mvc.method.annotation;
import java.util.List;
import java.util.Map;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.support.WebBindingInitializer;
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.support.InvocableHandlerMethod;
import org.springframework.web.servlet.HandlerMapping;
/**
* Creates a WebDataBinder of type {@link ServletRequestDataBinder} that can
* also use URI template variables values for data binding purposes.
* Creates a {@code ServletRequestDataBinder}.
*
* @author Rossen Stoyanchev
* @since 3.1
@ -43,45 +37,16 @@ public class ServletRequestDataBinderFactory extends InitBinderDataBinderFactory @@ -43,45 +37,16 @@ public class ServletRequestDataBinderFactory extends InitBinderDataBinderFactory
* @param binderMethods one or more {@code @InitBinder} methods
* @param initializer provides global data binder initialization
*/
public ServletRequestDataBinderFactory(List<InvocableHandlerMethod> binderMethods,
WebBindingInitializer initializer) {
public ServletRequestDataBinderFactory(List<InvocableHandlerMethod> binderMethods, WebBindingInitializer initializer) {
super(binderMethods, initializer);
}
/**
* Create a WebDataBinder of type {@link ServletRequestDataBinder} that can
* also use URI template variables values for data binding purposes.
*/
@Override
protected WebDataBinder createBinderInstance(Object target, String objectName, final NativeWebRequest request) {
return new ServletRequestDataBinder(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
* Returns an instance of {@link ExtendedServletRequestDataBinder}.
*/
@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));
}
}
}
@Override
protected ServletRequestDataBinder createBinderInstance(Object target, String objectName, NativeWebRequest request) {
return new ExtendedServletRequestDataBinder(target, objectName);
}
}

32
org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestDataBinderFactoryTests.java → org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExtendedServletRequestDataBinderTests.java

@ -21,42 +21,26 @@ import static org.junit.Assert.assertEquals; @@ -21,42 +21,26 @@ import static org.junit.Assert.assertEquals;
import java.util.HashMap;
import java.util.Map;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.TestBean;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.servlet.HandlerMapping;
/**
* Test fixture with {@link ServletRequestDataBinderFactory}.
* Test fixture for {@link ExtendedServletRequestDataBinder}.
*
* @author Rossen Stoyanchev
*/
public class ServletRequestDataBinderFactoryTests {
public class ExtendedServletRequestDataBinderTests {
private ServletRequestDataBinderFactory binderFactory;
private MockHttpServletRequest request;
private NativeWebRequest webRequest;
@Before
public void setup() {
binderFactory = new ServletRequestDataBinderFactory(null, null);
request = new MockHttpServletRequest();
webRequest = new ServletWebRequest(request);
RequestContextHolder.setRequestAttributes(webRequest);
}
@After
public void teardown() {
RequestContextHolder.resetRequestAttributes();
this.request = new MockHttpServletRequest();
}
@Test
@ -67,7 +51,7 @@ public class ServletRequestDataBinderFactoryTests { @@ -67,7 +51,7 @@ public class ServletRequestDataBinderFactoryTests {
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
TestBean target = new TestBean();
WebDataBinder binder = binderFactory.createBinder(webRequest, target, "");
WebDataBinder binder = new ExtendedServletRequestDataBinder(target, "");
((ServletRequestDataBinder) binder).bind(request);
assertEquals("nameValue", target.getName());
@ -75,7 +59,7 @@ public class ServletRequestDataBinderFactoryTests { @@ -75,7 +59,7 @@ public class ServletRequestDataBinderFactoryTests {
}
@Test
public void requestParamsOverrideUriTemplateVars() throws Exception {
public void uriTemplateVarAndRequestParam() throws Exception {
request.addParameter("age", "35");
Map<String, String> uriTemplateVars = new HashMap<String, String>();
@ -84,17 +68,17 @@ public class ServletRequestDataBinderFactoryTests { @@ -84,17 +68,17 @@ public class ServletRequestDataBinderFactoryTests {
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
TestBean target = new TestBean();
WebDataBinder binder = binderFactory.createBinder(webRequest, target, "");
WebDataBinder binder = new ExtendedServletRequestDataBinder(target, "");
((ServletRequestDataBinder) binder).bind(request);
assertEquals("nameValue", target.getName());
assertEquals(35, target.getAge());
assertEquals(25, target.getAge());
}
@Test
public void noUriTemplateVars() throws Exception {
TestBean target = new TestBean();
WebDataBinder binder = binderFactory.createBinder(webRequest, target, "");
WebDataBinder binder = new ExtendedServletRequestDataBinder(target, "");
((ServletRequestDataBinder) binder).bind(request);
assertEquals(null, target.getName());

11
org.springframework.web/src/main/java/org/springframework/web/bind/ServletRequestDataBinder.java

@ -108,9 +108,20 @@ public class ServletRequestDataBinder extends WebDataBinder { @@ -108,9 +108,20 @@ public class ServletRequestDataBinder extends WebDataBinder {
if (multipartRequest != null) {
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
}
addBindValues(mpvs, request);
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.
* <p>Use this method only if it's an error if the input isn't valid.

14
org.springframework.web/src/main/java/org/springframework/web/bind/support/DefaultDataBinderFactory.java

@ -41,10 +41,12 @@ public class DefaultDataBinderFactory implements WebDataBinderFactory { @@ -41,10 +41,12 @@ public class DefaultDataBinderFactory implements WebDataBinderFactory {
/**
* Create a new {@link WebDataBinder} for the given target object and
* 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);
if (initializer != null) {
if (this.initializer != null) {
this.initializer.initBinder(dataBinder, webRequest);
}
initBinder(dataBinder, webRequest);
@ -52,13 +54,15 @@ public class DefaultDataBinderFactory implements WebDataBinderFactory { @@ -52,13 +54,15 @@ public class DefaultDataBinderFactory implements WebDataBinderFactory {
}
/**
* Extension point to create the WebDataBinder instance, which is
* {@link WebRequestDataBinder} by default.
* Extension point to create the WebDataBinder instance.
* By default this is {@code WebRequestDataBinder}.
* @param target the binding target or {@code null} for type conversion only
* @param objectName the binding target object name
* @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);
}

15
org.springframework.web/src/main/java/org/springframework/web/method/annotation/InitBinderDataBinderFactory.java

@ -58,12 +58,11 @@ public class InitBinderDataBinderFactory extends DefaultDataBinderFactory { @@ -58,12 +58,11 @@ public class InitBinderDataBinderFactory extends DefaultDataBinderFactory {
@Override
public void initBinder(WebDataBinder binder, NativeWebRequest request) throws Exception {
for (InvocableHandlerMethod binderMethod : this.binderMethods) {
if (!invokeInitBinderMethod(binderMethod, binder)) {
continue;
}
Object returnValue = binderMethod.invokeForRequest(request, null, binder);
if (returnValue != null) {
throw new IllegalStateException("@InitBinder methods should return void: " + binderMethod);
if (isBinderMethodApplicable(binderMethod, binder)) {
Object returnValue = binderMethod.invokeForRequest(request, null, binder);
if (returnValue != null) {
throw new IllegalStateException("@InitBinder methods should return void: " + binderMethod);
}
}
}
}
@ -74,8 +73,8 @@ public class InitBinderDataBinderFactory extends DefaultDataBinderFactory { @@ -74,8 +73,8 @@ public class InitBinderDataBinderFactory extends DefaultDataBinderFactory {
* <p>The default implementation checks if target object name is included
* in the attribute names specified in the {@code @InitBinder} annotation.
*/
protected boolean invokeInitBinderMethod(HandlerMethod binderMethod, WebDataBinder binder) {
InitBinder annot = binderMethod.getMethodAnnotation(InitBinder.class);
protected boolean isBinderMethodApplicable(HandlerMethod initBinderMethod, WebDataBinder binder) {
InitBinder annot = initBinderMethod.getMethodAnnotation(InitBinder.class);
Collection<String> names = Arrays.asList(annot.value());
return (names.size() == 0 || names.contains(binder.getObjectName()));
}

Loading…
Cancel
Save