Browse Source

SPR-6909 Improve extension hooks in DefaultDataBinderFactory and subclasses.

git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@4620 50f2f4bb-b051-0410-bef5-90022cba6387
pull/1/merge
Rossen Stoyanchev 15 years ago
parent
commit
166ad38200
  1. 67
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestDataBinderFactory.java
  2. 29
      org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestDataBinderFactoryTests.java
  3. 39
      org.springframework.web/src/main/java/org/springframework/web/bind/support/DefaultDataBinderFactory.java
  4. 66
      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/ServletRequestDataBinderFactory.java

@ -22,18 +22,17 @@ import java.util.Map;
import org.springframework.beans.MutablePropertyValues; 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.WebDataBinder;
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.context.request.NativeWebRequest; import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
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; import org.springframework.web.servlet.HandlerMapping;
/** /**
* An {@link InitBinderDataBinderFactory} variation instantiating a data binder of type * Creates a {@link ServletRequestDataBinder} instance and extends it with the ability to include
* {@link ServletRequestDataBinder} and further extending it with the ability to add URI template variables * URI template variables in the values used for data binding purposes.
* to the values used in data binding.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 3.1 * @since 3.1
@ -41,62 +40,44 @@ import org.springframework.web.servlet.HandlerMapping;
public class ServletRequestDataBinderFactory extends InitBinderDataBinderFactory { public class ServletRequestDataBinderFactory extends InitBinderDataBinderFactory {
/** /**
* Create an {@link ServletRequestDataBinderFactory} instance. * Create a new instance.
* @param initBinderMethods init binder methods to use to initialize new data binders. * @param binderMethods {@link InitBinder} methods to initialize new data binder instances with
* @param bindingInitializer a WebBindingInitializer to use to initialize created data binder instances. * @param iitializer a global initializer to initialize new data binder instances with
*/ */
public ServletRequestDataBinderFactory(List<InvocableHandlerMethod> initBinderMethods, public ServletRequestDataBinderFactory(List<InvocableHandlerMethod> binderMethods, WebBindingInitializer iitializer) {
WebBindingInitializer bindingInitializer) { super(binderMethods, iitializer);
super(initBinderMethods, bindingInitializer);
}
/**
* Returns the more specific {@link ServletRequestDataBinder} created by {@link #createBinderInstance(Object, String)}.
*/
@Override
public ServletRequestDataBinder createBinder(NativeWebRequest request, Object target, String objectName)
throws Exception {
return (ServletRequestDataBinder) super.createBinder(request, target, objectName);
} }
/** /**
* {@inheritDoc} * Creates a {@link ServletRequestDataBinder} instance extended with the ability to add
* <p>This method creates a {@link ServletRequestDataBinder} instance that also adds URI template variables to * URI template variables the values used for data binding.
* the values used in data binding.
* <p>Subclasses wishing to override this method to provide their own ServletRequestDataBinder type can use the
* {@link #addUriTemplateVariables(MutablePropertyValues)} method to include URI template variables as follows:
* <pre>
* return new CustomServletRequestDataBinder(target, objectName) {
* protected void doBind(MutablePropertyValues mpvs) {
* addUriTemplateVariables(mpvs);
* super.doBind(mpvs);
* }
* };
* </pre>
*/ */
@Override @Override
protected WebDataBinder createBinderInstance(Object target, String objectName) { protected WebDataBinder createBinderInstance(Object target, String objectName, final NativeWebRequest request) {
return new ServletRequestDataBinder(target, objectName) { return new ServletRequestDataBinder(target, objectName) {
protected void doBind(MutablePropertyValues mpvs) { protected void doBind(MutablePropertyValues mpvs) {
addUriTemplateVariables(mpvs); addUriTemplateVars(mpvs, request);
super.doBind(mpvs); super.doBind(mpvs);
} }
}; };
} }
/** /**
* Adds URI template variables to the given property values. * Adds URI template variables to the the property values used for data binding.
* @param mpvs the PropertyValues to add URI template variables to * @param mpvs the PropertyValues to use for data binding
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected void addUriTemplateVariables(MutablePropertyValues mpvs) { protected final void addUriTemplateVars(MutablePropertyValues mpvs, NativeWebRequest request) {
RequestAttributes requestAttrs = RequestContextHolder.getRequestAttributes(); Map<String, String> uriTemplateVars =
if (requestAttrs != null) { (Map<String, String>) request.getAttribute(
String key = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE; HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
int scope = RequestAttributes.SCOPE_REQUEST; if (uriTemplateVars != null){
Map<String, String> uriTemplateVars = (Map<String, String>) requestAttrs.getAttribute(key, scope); for (String name : uriTemplateVars.keySet()) {
mpvs.addPropertyValues(uriTemplateVars); if (!mpvs.contains(name)) {
mpvs.addPropertyValue(name, uriTemplateVars.get(name));
}
}
} }
} }

29
org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestDataBinderFactoryTests.java

@ -73,5 +73,32 @@ public class ServletRequestDataBinderFactoryTests {
assertEquals("nameValue", target.getName()); assertEquals("nameValue", target.getName());
assertEquals(25, target.getAge()); assertEquals(25, target.getAge());
} }
@Test
public void requestParamsOverrideUriTemplateVars() throws Exception {
request.addParameter("age", "35");
Map<String, String> uriTemplateVars = new HashMap<String, String>();
uriTemplateVars.put("name", "nameValue");
uriTemplateVars.put("age", "25");
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars);
TestBean target = new TestBean();
WebDataBinder binder = binderFactory.createBinder(webRequest, target, "");
((ServletRequestDataBinder) binder).bind(request);
assertEquals("nameValue", target.getName());
assertEquals(35, target.getAge());
}
@Test
public void noUriTemplateVars() throws Exception {
TestBean target = new TestBean();
WebDataBinder binder = binderFactory.createBinder(webRequest, target, "");
((ServletRequestDataBinder) binder).bind(request);
assertEquals(null, target.getName());
assertEquals(0, target.getAge());
}
} }

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

@ -20,45 +20,58 @@ import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.NativeWebRequest;
/** /**
* A {@link WebDataBinderFactory} that creates {@link WebDataBinder} and initializes them * Creates a {@link WebRequestDataBinder} and initializes it through a {@link WebBindingInitializer}.
* with a {@link WebBindingInitializer}.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 3.1 * @since 3.1
*/ */
public class DefaultDataBinderFactory implements WebDataBinderFactory { public class DefaultDataBinderFactory implements WebDataBinderFactory {
private final WebBindingInitializer bindingInitializer; private final WebBindingInitializer initializer;
/** /**
* Create {@link DefaultDataBinderFactory} instance. * Create {@link DefaultDataBinderFactory} instance.
* @param bindingInitializer a {@link WebBindingInitializer} to initialize new data binder instances with * @param initializer a global initializer to initialize new data binder instances with
*/ */
public DefaultDataBinderFactory(WebBindingInitializer bindingInitializer) { public DefaultDataBinderFactory(WebBindingInitializer initializer) {
this.bindingInitializer = bindingInitializer; this.initializer = initializer;
} }
/** /**
* Create a new {@link WebDataBinder} for the given target object and initialize it through * Create a new {@link WebDataBinder} for the given target object and initialize it through
* a {@link WebBindingInitializer}. * a {@link WebBindingInitializer}.
*/ */
public 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); WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
if (bindingInitializer != null) { if (initializer != null) {
this.bindingInitializer.initBinder(dataBinder, webRequest); this.initializer.initBinder(dataBinder, webRequest);
} }
initBinder(dataBinder, webRequest);
return dataBinder; return dataBinder;
} }
/** /**
* Create a {@link WebDataBinder} instance. * Extension hook that subclasses can use to create a data binder of a specific type.
* @param target the object to create a data binder for, or {@code null} if creating a binder for a simple type * The default implementation creates a {@link WebRequestDataBinder}.
* @param target the data binding target object; or {@code null} for type conversion on simple objects.
* @param objectName the name of the target object * @param objectName the name of the target object
* @param webRequest the current request
*/ */
protected WebDataBinder createBinderInstance(Object target, String objectName) { protected WebDataBinder createBinderInstance(Object target, String objectName, NativeWebRequest webRequest) {
return new WebRequestDataBinder(target, objectName); return new WebRequestDataBinder(target, objectName);
} }
/**
* Extension hook that subclasses can override to initialize further the data binder.
* Will be invoked after the data binder is initialized through the {@link WebBindingInitializer}.
* @param dataBinder the data binder instance to customize
* @param webRequest the current request
* @throws Exception if initialization fails
*/
protected void initBinder(WebDataBinder dataBinder, NativeWebRequest webRequest) throws Exception {
}
} }

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

@ -18,63 +18,67 @@ package org.springframework.web.method.annotation;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Set;
import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.support.DefaultDataBinderFactory; import org.springframework.web.bind.support.DefaultDataBinderFactory;
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.method.HandlerMethod;
import org.springframework.web.method.support.InvocableHandlerMethod; import org.springframework.web.method.support.InvocableHandlerMethod;
/** /**
* A specialization of {@link DefaultDataBinderFactory} that further initializes {@link WebDataBinder} instances * Adds data binder initialization through the invocation of @{@link InitBinder} methods.
* by invoking {@link InitBinder} methods.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 3.1 * @since 3.1
*/ */
public class InitBinderDataBinderFactory extends DefaultDataBinderFactory { public class InitBinderDataBinderFactory extends DefaultDataBinderFactory {
private final List<InvocableHandlerMethod> initBinderMethods; private final List<InvocableHandlerMethod> binderMethods;
/** /**
* Create an {@code InitBinderDataBinderFactory} instance with the given {@link InitBinder} methods. * Create a new instance.
* @param binderMethods {@link InitBinder} methods to use to invoke to initialize new data binder instances * @param binderMethods {@link InitBinder} methods to initialize new data binder instances with
* @param bindingInitializer a {@link WebBindingInitializer} to initialize new data binder instances with * @param initializer a global initializer to initialize new data binder instances with
*/ */
public InitBinderDataBinderFactory(List<InvocableHandlerMethod> binderMethods, public InitBinderDataBinderFactory(List<InvocableHandlerMethod> binderMethods, WebBindingInitializer initializer) {
WebBindingInitializer bindingInitializer) { super(initializer);
super(bindingInitializer); this.binderMethods = (binderMethods != null) ? binderMethods : new ArrayList<InvocableHandlerMethod>();
this.initBinderMethods = (binderMethods != null) ? binderMethods : new ArrayList<InvocableHandlerMethod>();
} }
/** /**
* Create a {@link WebDataBinder} for the given object and initialize it by calling {@link InitBinder} methods. * Initializes the given data binder through the invocation of @{@link InitBinder} methods.
* Only methods with an {@link InitBinder} annotation value that doesn't list attributes names or methods with * An @{@link InitBinder} method that defines names via {@link InitBinder#value()} will
* an {@link InitBinder} annotation value that matches the target object name are invoked. * not be invoked unless one of the names matches the target object name.
* @see InitBinder#value() * @see InitBinder#value()
* @throws Exception if one of the invoked @{@link InitBinder} methods fail.
*/ */
@Override @Override
public WebDataBinder createBinder(NativeWebRequest request, Object target, String objectName) throws Exception { public void initBinder(WebDataBinder binder, NativeWebRequest request) throws Exception {
WebDataBinder dataBinder = super.createBinder(request, target, objectName); for (InvocableHandlerMethod binderMethod : this.binderMethods) {
if (!isBinderMethodApplicable(binderMethod, binder)) {
for (InvocableHandlerMethod binderMethod : this.initBinderMethods) { continue;
InitBinder annot = binderMethod.getMethodAnnotation(InitBinder.class); }
Set<String> attributeNames = new HashSet<String>(Arrays.asList(annot.value())); Object returnValue = binderMethod.invokeForRequest(request, null, binder);
if (returnValue != null) {
if (attributeNames.size() == 0 || attributeNames.contains(objectName)) { throw new IllegalStateException("This @InitBinder method does not return void: " + binderMethod);
Object returnValue = binderMethod.invokeForRequest(request, null, dataBinder);
if (returnValue != null) {
throw new IllegalStateException("InitBinder methods must not have a return value: " + binderMethod);
}
} }
} }
}
return dataBinder;
/**
* Returns {@code true} if the given @{@link InitBinder} method should be invoked to initialize
* the given {@link WebDataBinder} instance. This implementations returns {@code true} if
* the @{@link InitBinder} annotation on the method does not define any names or if one of the
* names it defines names matches the target object name.
*/
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()));
} }
} }

Loading…
Cancel
Save