Skip to content

Commit bec1fc1

Browse files
committed
ModelAttributeMethodProcessor detects re-enabled binding declaration
Issue: SPR-16083
1 parent ea00c7c commit bec1fc1

File tree

4 files changed

+96
-43
lines changed

4 files changed

+96
-43
lines changed

spring-web/src/main/java/org/springframework/web/method/annotation/ModelAttributeMethodProcessor.java

+3-5
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,9 @@ public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAn
115115
Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");
116116

117117
String name = ModelFactory.getNameForParameter(parameter);
118-
if (!mavContainer.isBindingDisabled(name)) {
119-
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
120-
if (ann != null && !ann.binding()) {
121-
mavContainer.setBindingDisabled(name);
122-
}
118+
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
119+
if (ann != null) {
120+
mavContainer.setBinding(name, ann.binding());
123121
}
124122

125123
Object attribute = null;

spring-web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java

+49-30
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
* returns the redirect model instead of the default model.
4545
*
4646
* @author Rossen Stoyanchev
47+
* @author Juergen Hoeller
4748
* @since 3.1
4849
*/
4950
public class ModelAndViewContainer {
@@ -60,12 +61,13 @@ public class ModelAndViewContainer {
6061

6162
private boolean redirectModelScenario = false;
6263

63-
/* Names of attributes with binding disabled */
64-
private final Set<String> bindingDisabledAttributes = new HashSet<>(4);
65-
6664
@Nullable
6765
private HttpStatus status;
6866

67+
private final Set<String> noBinding = new HashSet<>(4);
68+
69+
private final Set<String> bindingDisabled = new HashSet<>(4);
70+
6971
private final SessionStatus sessionStatus = new SimpleSessionStatus();
7072

7173
private boolean requestHandled = false;
@@ -147,24 +149,6 @@ public ModelMap getModel() {
147149
}
148150
}
149151

150-
/**
151-
* Register an attribute for which data binding should not occur, for example
152-
* corresponding to an {@code @ModelAttribute(binding=false)} declaration.
153-
* @param attributeName the name of the attribute
154-
* @since 4.3
155-
*/
156-
public void setBindingDisabled(String attributeName) {
157-
this.bindingDisabledAttributes.add(attributeName);
158-
}
159-
160-
/**
161-
* Whether binding is disabled for the given model attribute.
162-
* @since 4.3
163-
*/
164-
public boolean isBindingDisabled(String name) {
165-
return this.bindingDisabledAttributes.contains(name);
166-
}
167-
168152
/**
169153
* Whether to use the default model or the redirect model.
170154
*/
@@ -205,15 +189,7 @@ public void setRedirectModelScenario(boolean redirectModelScenario) {
205189
}
206190

207191
/**
208-
* Return the {@link SessionStatus} instance to use that can be used to
209-
* signal that session processing is complete.
210-
*/
211-
public SessionStatus getSessionStatus() {
212-
return this.sessionStatus;
213-
}
214-
215-
/**
216-
* Provide a HTTP status that will be passed on to with the
192+
* Provide an HTTP status that will be passed on to with the
217193
* {@code ModelAndView} used for view rendering purposes.
218194
* @since 4.3
219195
*/
@@ -230,6 +206,49 @@ public HttpStatus getStatus() {
230206
return this.status;
231207
}
232208

209+
/**
210+
* Programmatically register an attribute for which data binding should not occur,
211+
* not even for a subsequent {@code @ModelAttribute} declaration.
212+
* @param attributeName the name of the attribute
213+
* @since 4.3
214+
*/
215+
public void setBindingDisabled(String attributeName) {
216+
this.bindingDisabled.add(attributeName);
217+
}
218+
219+
/**
220+
* Whether binding is disabled for the given model attribute.
221+
* @since 4.3
222+
*/
223+
public boolean isBindingDisabled(String name) {
224+
return (this.bindingDisabled.contains(name) || this.noBinding.contains(name));
225+
}
226+
227+
/**
228+
* Register whether data binding should occur for a corresponding model attribute,
229+
* corresponding to an {@code @ModelAttribute(binding=true/false)} declaration.
230+
* <p>Note: While this flag will be taken into account by {@link #isBindingDisabled},
231+
* a hard {@link #setBindingDisabled} declaration will always override it.
232+
* @param attributeName the name of the attribute
233+
* @since 4.3.13
234+
*/
235+
public void setBinding(String attributeName, boolean enabled) {
236+
if (!enabled) {
237+
this.noBinding.add(attributeName);
238+
}
239+
else {
240+
this.noBinding.remove(attributeName);
241+
}
242+
}
243+
244+
/**
245+
* Return the {@link SessionStatus} instance to use that can be used to
246+
* signal that session processing is complete.
247+
*/
248+
public SessionStatus getSessionStatus() {
249+
return this.sessionStatus;
250+
}
251+
233252
/**
234253
* Whether the request has been handled fully within the handler, e.g.
235254
* {@code @ResponseBody} method, and therefore view resolution is not

spring-web/src/test/java/org/springframework/web/method/annotation/ModelFactoryTests.java

+6-8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -49,7 +49,6 @@
4949
import static org.mockito.BDDMockito.given;
5050
import static org.mockito.BDDMockito.mock;
5151

52-
5352
/**
5453
* Text fixture for {@link ModelFactory} tests.
5554
*
@@ -158,7 +157,7 @@ public void sessionAttributeNotPresent() throws Exception {
158157
modelFactory.initModel(this.webRequest, this.mavContainer, handlerMethod);
159158
fail("Expected HttpSessionRequiredException");
160159
}
161-
catch (HttpSessionRequiredException e) {
160+
catch (HttpSessionRequiredException ex) {
162161
// expected
163162
}
164163

@@ -229,9 +228,7 @@ public void updateModelSessionAttributesRemoved() throws Exception {
229228
assertNull(this.attributeStore.retrieveAttribute(this.webRequest, attributeName));
230229
}
231230

232-
// SPR-12542
233-
234-
@Test
231+
@Test // SPR-12542
235232
public void updateModelWhenRedirecting() throws Exception {
236233
String attributeName = "sessionAttr";
237234
String attribute = "value";
@@ -274,8 +271,8 @@ private InvocableHandlerMethod createHandlerMethod(String methodName, Class<?>..
274271
}
275272

276273

277-
@SessionAttributes({"sessionAttr", "foo"}) @SuppressWarnings("unused")
278-
private static class TestController {
274+
@SessionAttributes({"sessionAttr", "foo"})
275+
static class TestController {
279276

280277
@ModelAttribute
281278
public void modelAttr(Model model) {
@@ -309,6 +306,7 @@ public void handleSessionAttr(@ModelAttribute("sessionAttr") String sessionAttr)
309306
}
310307
}
311308

309+
312310
private static class Foo {
313311
}
314312

spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletAnnotationControllerHandlerMethodTests.java

+38
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@
146146

147147
/**
148148
* @author Rossen Stoyanchev
149+
* @author Juergen Hoeller
149150
*/
150151
public class ServletAnnotationControllerHandlerMethodTests extends AbstractServletHandlerMethodTests {
151152

@@ -535,6 +536,20 @@ public void modelFormController() throws Exception {
535536
assertEquals("myPath-name1-typeMismatch-tb1-myValue-yourValue", response.getContentAsString());
536537
}
537538

539+
@Test
540+
public void lateBindingFormController() throws Exception {
541+
initServlet(
542+
wac -> wac.registerBeanDefinition("viewResolver", new RootBeanDefinition(TestViewResolver.class)),
543+
LateBindingFormController.class);
544+
545+
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/myPath.do");
546+
request.addParameter("name", "name1");
547+
request.addParameter("age", "value2");
548+
MockHttpServletResponse response = new MockHttpServletResponse();
549+
getServlet().service(request, response);
550+
assertEquals("myView-name1-typeMismatch-tb1-myValue", response.getContentAsString());
551+
}
552+
538553
@Test
539554
public void proxiedFormController() throws Exception {
540555
initServlet(wac -> {
@@ -2224,6 +2239,29 @@ public String myHandle(@ModelAttribute("myCommand") TestBean tb, BindingResult e
22242239
}
22252240
}
22262241

2242+
@Controller
2243+
public static class LateBindingFormController {
2244+
2245+
@ModelAttribute("testBeanList")
2246+
public List<TestBean> getTestBeans(@ModelAttribute(name="myCommand", binding=false) TestBean tb) {
2247+
List<TestBean> list = new LinkedList<>();
2248+
list.add(new TestBean("tb1"));
2249+
list.add(new TestBean("tb2"));
2250+
return list;
2251+
}
2252+
2253+
@RequestMapping("/myPath.do")
2254+
public String myHandle(@ModelAttribute(name="myCommand", binding=true) TestBean tb, BindingResult errors, ModelMap model) {
2255+
FieldError error = errors.getFieldError("age");
2256+
assertNotNull("Must have field error for age property", error);
2257+
assertEquals("value2", error.getRejectedValue());
2258+
if (!model.containsKey("myKey")) {
2259+
model.addAttribute("myKey", "myValue");
2260+
}
2261+
return "myView";
2262+
}
2263+
}
2264+
22272265
@Controller
22282266
static class MyCommandProvidingFormController<T, TB, TB2> extends MyFormController {
22292267

0 commit comments

Comments
 (0)