Skip to content

Commit 451cce4

Browse files
committed
Do not override requestContextAttribute with null in UrlBasedViewResolvers
Prior to this commit, if a subclass of org.springframework.web.servlet.view.AbstractView or org.springframework.web.reactive.result.view.AbstractUrlBasedView configured a custom value for the requestContextAttribute, that value was overwritten with null whenever the View was dynamically instantiated by a UrlBasedViewResolver, and this could lead to confusing behavior for users of the View. This commit addresses this issue by ensuring that the UrlBasedViewResolvers in spring-webmvc and spring-webflux do not override the requestContextAttribute in a View if the UrlBasedViewResolver has not been explicitly configured with a custom requestContextAttribute value. Closes gh-23129
1 parent c4075cf commit 451cce4

File tree

4 files changed

+235
-167
lines changed

4 files changed

+235
-167
lines changed

spring-webflux/src/main/java/org/springframework/web/reactive/result/view/UrlBasedViewResolver.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2019 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.
@@ -30,8 +30,8 @@
3030
import org.springframework.util.PatternMatchUtils;
3131

3232
/**
33-
* A {@link ViewResolver} that allow direct resolution of symbolic view names
34-
* to URLs without explicit mapping definition. This is useful if symbolic names
33+
* A {@link ViewResolver} that allows direct resolution of symbolic view names
34+
* to URLs without explicit mapping definitions. This is useful if symbolic names
3535
* match the names of view resources in a straightforward manner (i.e. the
3636
* symbolic name is the unique part of the resource's filename), without the need
3737
* for a dedicated mapping to be defined for each view.
@@ -59,6 +59,7 @@
5959
* @author Rossen Stoyanchev
6060
* @author Sebastien Deleuze
6161
* @author Juergen Hoeller
62+
* @author Sam Brannen
6263
* @since 5.0
6364
*/
6465
public class UrlBasedViewResolver extends ViewResolverSupport
@@ -266,7 +267,7 @@ protected boolean canHandle(String viewName, Locale locale) {
266267

267268
/**
268269
* Creates a new View instance of the specified view class and configures it.
269-
* Does <i>not</i> perform any lookup for pre-defined View instances.
270+
* <p>Does <i>not</i> perform any lookup for pre-defined View instances.
270271
* <p>Spring lifecycle methods as defined by the bean container do not have to
271272
* be called here: They will be automatically applied afterwards, provided
272273
* that an {@link #setApplicationContext ApplicationContext} is available.
@@ -281,10 +282,14 @@ protected AbstractUrlBasedView createView(String viewName) {
281282

282283
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
283284
view.setSupportedMediaTypes(getSupportedMediaTypes());
284-
view.setRequestContextAttribute(getRequestContextAttribute());
285285
view.setDefaultCharset(getDefaultCharset());
286286
view.setUrl(getPrefix() + viewName + getSuffix());
287287

288+
String requestContextAttribute = getRequestContextAttribute();
289+
if (requestContextAttribute != null) {
290+
view.setRequestContextAttribute(requestContextAttribute);
291+
}
292+
288293
return view;
289294
}
290295

spring-webflux/src/test/java/org/springframework/web/reactive/result/view/UrlBasedViewResolverTests.java

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,60 @@
3737
*
3838
* @author Rossen Stoyanchev
3939
* @author Sebastien Deleuze
40+
* @author Sam Brannen
4041
*/
4142
public class UrlBasedViewResolverTests {
4243

43-
private UrlBasedViewResolver resolver;
44+
private final UrlBasedViewResolver resolver = new UrlBasedViewResolver();
4445

4546

4647
@Before
4748
public void setup() {
4849
StaticApplicationContext context = new StaticApplicationContext();
4950
context.refresh();
50-
this.resolver = new UrlBasedViewResolver();
5151
this.resolver.setApplicationContext(context);
5252
}
5353

54+
@Test
55+
public void urlBasedViewResolverOverridesCustomRequestContextAttributeWithNonNullValue() throws Exception {
56+
assertThat(new TestView().getRequestContextAttribute())
57+
.as("requestContextAttribute when instantiated directly")
58+
.isEqualTo("testRequestContext");
59+
60+
this.resolver.setViewClass(TestView.class);
61+
this.resolver.setRequestContextAttribute("viewResolverRequestContext");
62+
63+
Mono<View> mono = this.resolver.resolveViewName("example", Locale.getDefault());
64+
StepVerifier.create(mono)
65+
.consumeNextWith(view -> {
66+
assertThat(view).isInstanceOf(TestView.class);
67+
assertThat(((TestView) view).getRequestContextAttribute())
68+
.as("requestContextAttribute when instantiated dynamically by UrlBasedViewResolver")
69+
.isEqualTo("viewResolverRequestContext");
70+
})
71+
.expectComplete()
72+
.verify(Duration.ZERO);
73+
}
74+
75+
@Test
76+
public void urlBasedViewResolverDoesNotOverrideCustomRequestContextAttributeWithNull() throws Exception {
77+
assertThat(new TestView().getRequestContextAttribute())
78+
.as("requestContextAttribute when instantiated directly")
79+
.isEqualTo("testRequestContext");
80+
81+
this.resolver.setViewClass(TestView.class);
82+
83+
Mono<View> mono = this.resolver.resolveViewName("example", Locale.getDefault());
84+
StepVerifier.create(mono)
85+
.consumeNextWith(view -> {
86+
assertThat(view).isInstanceOf(TestView.class);
87+
assertThat(((TestView) view).getRequestContextAttribute())
88+
.as("requestContextAttribute when instantiated dynamically by UrlBasedViewResolver")
89+
.isEqualTo("testRequestContext");
90+
})
91+
.expectComplete()
92+
.verify(Duration.ZERO);
93+
}
5494

5595
@Test
5696
public void viewNames() throws Exception {
@@ -98,6 +138,10 @@ public void customizedRedirectView() throws Exception {
98138

99139
private static class TestView extends AbstractUrlBasedView {
100140

141+
public TestView() {
142+
setRequestContextAttribute("testRequestContext");
143+
}
144+
101145
@Override
102146
public boolean checkResourceExists(Locale locale) throws Exception {
103147
return true;

spring-webmvc/src/main/java/org/springframework/web/servlet/view/UrlBasedViewResolver.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 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.
@@ -34,7 +34,7 @@
3434
/**
3535
* Simple implementation of the {@link org.springframework.web.servlet.ViewResolver}
3636
* interface, allowing for direct resolution of symbolic view names to URLs,
37-
* without explicit mapping definition. This is useful if your symbolic names
37+
* without explicit mapping definitions. This is useful if your symbolic names
3838
* match the names of your view resources in a straightforward manner
3939
* (i.e. the symbolic name is the unique part of the resource's filename),
4040
* without the need for a dedicated mapping to be defined for each view.
@@ -65,14 +65,15 @@
6565
* a symbolic view name to different resources depending on the current locale.
6666
*
6767
* <p><b>Note:</b> When chaining ViewResolvers, a UrlBasedViewResolver will check whether
68-
* the {@link AbstractUrlBasedView#checkResource specified resource actually exists}.
68+
* the {@linkplain AbstractUrlBasedView#checkResource specified resource actually exists}.
6969
* However, with {@link InternalResourceView}, it is not generally possible to
7070
* determine the existence of the target resource upfront. In such a scenario,
71-
* a UrlBasedViewResolver will always return View for any given view name;
71+
* a UrlBasedViewResolver will always return a View for any given view name;
7272
* as a consequence, it should be configured as the last ViewResolver in the chain.
7373
*
7474
* @author Juergen Hoeller
7575
* @author Rob Harrop
76+
* @author Sam Brannen
7677
* @since 13.12.2003
7778
* @see #setViewClass
7879
* @see #setPrefix
@@ -550,14 +551,17 @@ protected AbstractUrlBasedView buildView(String viewName) throws Exception {
550551

551552
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
552553
view.setUrl(getPrefix() + viewName + getSuffix());
554+
view.setAttributesMap(getAttributesMap());
553555

554556
String contentType = getContentType();
555557
if (contentType != null) {
556558
view.setContentType(contentType);
557559
}
558560

559-
view.setRequestContextAttribute(getRequestContextAttribute());
560-
view.setAttributesMap(getAttributesMap());
561+
String requestContextAttribute = getRequestContextAttribute();
562+
if (requestContextAttribute != null) {
563+
view.setRequestContextAttribute(requestContextAttribute);
564+
}
561565

562566
Boolean exposePathVariables = getExposePathVariables();
563567
if (exposePathVariables != null) {

0 commit comments

Comments
 (0)