parent
806e79b14b
commit
2e7470b27f
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* 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<mypackage.OrderAddress>".
|
||||
* @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;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* 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
|
||||
|
|
Loading…
Reference in New Issue