提交 2e7470b2 编写于 作者: R Rossen Stoyanchev

Allow binding=false on @ModelAttribute

Issue: SPR-13402
上级 806e79b1
/*
* Copyright 2002-2012 the original author or authors.
* 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.
......@@ -22,6 +22,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.ui.Model;
/**
......@@ -49,6 +50,7 @@ import org.springframework.ui.Model;
* access to a {@link Model} argument.
*
* @author Juergen Hoeller
* @author Rossen Stoyanchev
* @since 2.5
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
......@@ -56,6 +58,12 @@ import org.springframework.ui.Model;
@Documented
public @interface ModelAttribute {
/**
* Alias for {@link #name}.
*/
@AliasFor("name")
String value() default "";
/**
* The name of the model attribute to bind to.
* <p>The default model attribute name is inferred from the declared
......@@ -63,7 +71,19 @@ public @interface ModelAttribute {
* based on the non-qualified class name:
* e.g. "orderAddress" for class "mypackage.OrderAddress",
* or "orderAddressList" for "List&lt;mypackage.OrderAddress&gt;".
* @since 4.3
*/
String value() default "";
@AliasFor("value")
String name() default "";
/**
* Allows declaring data binding disabled directly on an
* {@code @ModelAttribute} method parameter or on the attribute returned from
* an {@code @ModelAttribute} method, both of which would prevent data
* binding for that attribute.
* <p>By default this is set to "true" in which case data binding applies.
* Set this to "false" to disable data binding.
*/
boolean binding() default true;
}
/*
* Copyright 2002-2015 the original author or authors.
* 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.
......@@ -101,9 +101,18 @@ public class ModelAttributeMethodProcessor
Object attribute = (mavContainer.containsAttribute(name) ? mavContainer.getModel().get(name) :
createAttribute(name, parameter, binderFactory, webRequest));
if (!mavContainer.isBindingDisabled(name)) {
ModelAttribute annotation = parameter.getParameterAnnotation(ModelAttribute.class);
if (annotation != null && !annotation.binding()) {
mavContainer.setBindingDisabled(name);
}
}
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
bindRequestParameters(binder, webRequest);
if (!mavContainer.isBindingDisabled(name)) {
bindRequestParameters(binder, webRequest);
}
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
......
......@@ -132,9 +132,11 @@ public final class ModelFactory {
while (!this.modelMethods.isEmpty()) {
InvocableHandlerMethod modelMethod = getNextModelMethod(container).getHandlerMethod();
ModelAttribute annot = modelMethod.getMethodAnnotation(ModelAttribute.class);
String modelName = annot.value();
if (container.containsAttribute(modelName)) {
ModelAttribute annotation = modelMethod.getMethodAnnotation(ModelAttribute.class);
if (container.containsAttribute(annotation.name())) {
if (!annotation.binding()) {
container.setBindingDisabled(annotation.name());
}
continue;
}
......@@ -142,6 +144,9 @@ public final class ModelFactory {
if (!modelMethod.isVoid()){
String returnValueName = getNameForReturnValue(returnValue, modelMethod.getReturnType());
if (!annotation.binding()) {
container.setBindingDisabled(returnValueName);
}
if (!container.containsAttribute(returnValueName)) {
container.addAttribute(returnValueName, returnValue);
}
......
......@@ -16,7 +16,9 @@
package org.springframework.web.method.support;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.springframework.http.HttpStatus;
import org.springframework.ui.Model;
......@@ -55,6 +57,9 @@ public class ModelAndViewContainer {
private boolean redirectModelScenario = false;
/* Names of attributes with binding disabled */
private final Set<String> bindingDisabledAttributes = new HashSet<String>(4);
private HttpStatus status;
private final SessionStatus sessionStatus = new SimpleSessionStatus();
......@@ -133,6 +138,23 @@ public class ModelAndViewContainer {
}
}
/**
* Register an attribute for which data binding should not occur, for example
* corresponding to an {@code @ModelAttribute(binding=false)} declaration.
* @param attributeName the name of the attribute
* @since 4.3
*/
public void setBindingDisabled(String attributeName) {
this.bindingDisabledAttributes.add(attributeName);
}
/**
* Whether binding is disabled for the given model attribute.
*/
public boolean isBindingDisabled(String name) {
return this.bindingDisabledAttributes.contains(name);
}
/**
* Whether to use the default model or the redirect model.
*/
......
......@@ -62,6 +62,7 @@ public class ModelAttributeMethodProcessorTests {
private MethodParameter paramErrors;
private MethodParameter paramInt;
private MethodParameter paramModelAttr;
private MethodParameter paramBindingDisabledAttr;
private MethodParameter paramNonSimpleType;
private MethodParameter returnParamNamedModelAttr;
......@@ -75,13 +76,15 @@ public class ModelAttributeMethodProcessorTests {
this.processor = new ModelAttributeMethodProcessor(false);
Method method = ModelAttributeHandler.class.getDeclaredMethod("modelAttribute",
TestBean.class, Errors.class, int.class, TestBean.class, TestBean.class);
TestBean.class, Errors.class, int.class, TestBean.class,
TestBean.class, TestBean.class);
this.paramNamedValidModelAttr = new SynthesizingMethodParameter(method, 0);
this.paramErrors = new SynthesizingMethodParameter(method, 1);
this.paramInt = new SynthesizingMethodParameter(method, 2);
this.paramModelAttr = new SynthesizingMethodParameter(method, 3);
this.paramNonSimpleType = new SynthesizingMethodParameter(method, 4);
this.paramBindingDisabledAttr = new SynthesizingMethodParameter(method, 4);
this.paramNonSimpleType = new SynthesizingMethodParameter(method, 5);
method = getClass().getDeclaredMethod("annotatedReturnValue");
this.returnParamNamedModelAttr = new MethodParameter(method, -1);
......@@ -167,6 +170,41 @@ public class ModelAttributeMethodProcessorTests {
assertTrue(dataBinder.isValidateInvoked());
}
@Test
public void resolveArgumentBindingDisabledPreviously() throws Exception {
String name = "attrName";
Object target = new TestBean();
this.container.addAttribute(name, target);
// Declare binding disabled (e.g. via @ModelAttribute method)
this.container.setBindingDisabled(name);
StubRequestDataBinder dataBinder = new StubRequestDataBinder(target, name);
WebDataBinderFactory factory = mock(WebDataBinderFactory.class);
given(factory.createBinder(this.request, target, name)).willReturn(dataBinder);
this.processor.resolveArgument(this.paramNamedValidModelAttr, this.container, this.request, factory);
assertFalse(dataBinder.isBindInvoked());
assertTrue(dataBinder.isValidateInvoked());
}
@Test
public void resolveArgumentBindingDisabled() throws Exception {
String name = "noBindAttr";
Object target = new TestBean();
this.container.addAttribute(name, target);
StubRequestDataBinder dataBinder = new StubRequestDataBinder(target, name);
WebDataBinderFactory factory = mock(WebDataBinderFactory.class);
given(factory.createBinder(this.request, target, name)).willReturn(dataBinder);
this.processor.resolveArgument(this.paramBindingDisabledAttr, this.container, this.request, factory);
assertFalse(dataBinder.isBindInvoked());
assertTrue(dataBinder.isValidateInvoked());
}
@Test(expected = BindException.class)
public void resolveArgumentBindException() throws Exception {
String name = "testBean";
......@@ -281,6 +319,7 @@ public class ModelAttributeMethodProcessorTests {
Errors errors,
int intArg,
@ModelAttribute TestBean defaultNameAttr,
@ModelAttribute(name="noBindAttr", binding=false) @Valid TestBean noBindAttr,
TestBean notAnnotatedAttr) {
}
}
......
......@@ -115,6 +115,30 @@ public class ModelFactoryTests {
assertNull(this.mavContainer.getModel().get("name"));
}
@Test
public void modelAttributeWithBindingDisabled() throws Exception {
ModelFactory modelFactory = createModelFactory("modelAttrWithBindingDisabled");
HandlerMethod handlerMethod = createHandlerMethod("handle");
modelFactory.initModel(this.webRequest, this.mavContainer, handlerMethod);
assertTrue(this.mavContainer.containsAttribute("foo"));
assertTrue(this.mavContainer.isBindingDisabled("foo"));
}
@Test
public void modelAttributeFromSessionWithBindingDisabled() throws Exception {
Foo foo = new Foo();
this.attributeStore.storeAttribute(this.webRequest, "foo", foo);
ModelFactory modelFactory = createModelFactory("modelAttrWithBindingDisabled");
HandlerMethod handlerMethod = createHandlerMethod("handle");
modelFactory.initModel(this.webRequest, this.mavContainer, handlerMethod);
assertTrue(this.mavContainer.containsAttribute("foo"));
assertSame(foo, this.mavContainer.getModel().get("foo"));
assertTrue(this.mavContainer.isBindingDisabled("foo"));
}
@Test
public void sessionAttribute() throws Exception {
this.attributeStore.storeAttribute(this.webRequest, "sessionAttr", "sessionAttrValue");
......@@ -250,7 +274,7 @@ public class ModelFactoryTests {
}
@SessionAttributes("sessionAttr") @SuppressWarnings("unused")
@SessionAttributes({"sessionAttr", "foo"}) @SuppressWarnings("unused")
private static class TestController {
@ModelAttribute
......@@ -273,6 +297,11 @@ public class ModelFactoryTests {
return null;
}
@ModelAttribute(name="foo", binding=false)
public Foo modelAttrWithBindingDisabled() {
return new Foo();
}
public void handle() {
}
......@@ -280,4 +309,7 @@ public class ModelFactoryTests {
}
}
private static class Foo {
}
}
......@@ -1704,6 +1704,31 @@ With a `BindingResult` you can check if errors were found in which case it's com
render the same form where the errors can be shown with the help of Spring's `<errors>`
form tag.
Note that in some cases it may be useful to gain access to an attribute in the
model without data binding. For such cases you may inject the `Model` into the
controller or alternatively use the `binding` flag on the annotation:
[source,java,indent=0]
[subs="verbatim,quotes"]
----
@ModelAttribute
public AccountForm setUpForm() {
return new AccountForm();
}
@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
return accountRepository.findOne(accountId);
}
@RequestMapping(path="update", method=POST)
public String update(@Valid AccountUpdateForm form, BindingResult result,
**@ModelAttribute(binding=false)** Account account) {
// ...
}
----
In addition to data binding you can also invoke validation using your own custom
validator passing the same `BindingResult` that was used to record data binding errors.
That allows for data binding and validation errors to be accumulated in one place and
......@@ -1747,6 +1772,7 @@ See <<validation-beanvalidation>> and <<validation>> for details on how to confi
use validation.
[[mvc-ann-sessionattrib]]
==== Using @SessionAttributes to store model attributes in the HTTP session between requests
......
......@@ -666,6 +666,7 @@ Spring 4.3 also improves the caching abstraction as follows:
* `@ResponseStatus` supported on the class level and inherited on all methods.
* New `@SessionAttribute` annotation for access to session attributes (see <<mvc-ann-sessionattrib-global, example>>).
* New `@RequestAttribute` annotation for access to session attributes (see <<mvc-ann-requestattrib, example>>).
* `@ModelAttribute` allows preventing data binding via `binding=false` attribute (see <<mvc-ann-modelattrib-method-args, reference>>).
* `AsyncRestTemplate` supports request interception.
=== WebSocket Messaging Improvements
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册