Skip to content

Commit 2497d42

Browse files
committed
Detect existing DispatcherServlet strategy beans in parent context as well
Closes gh-25290
1 parent 32238cc commit 2497d42

File tree

2 files changed

+94
-55
lines changed

2 files changed

+94
-55
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java

Lines changed: 70 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
import java.util.LinkedHashMap;
2020
import java.util.Map;
2121

22+
import org.springframework.beans.factory.BeanFactory;
2223
import org.springframework.beans.factory.config.BeanDefinition;
2324
import org.springframework.beans.factory.config.RuntimeBeanReference;
2425
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
26+
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
2527
import org.springframework.beans.factory.support.RootBeanDefinition;
2628
import org.springframework.beans.factory.xml.ParserContext;
2729
import org.springframework.lang.Nullable;
@@ -43,6 +45,7 @@
4345
* Convenience methods for use in MVC namespace BeanDefinitionParsers.
4446
*
4547
* @author Rossen Stoyanchev
48+
* @author Juergen Hoeller
4649
* @author Brian Clozel
4750
* @author Marten Deinum
4851
* @since 3.1
@@ -67,15 +70,15 @@ public abstract class MvcNamespaceUtils {
6770
private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
6871

6972

70-
public static void registerDefaultComponents(ParserContext parserContext, @Nullable Object source) {
71-
registerBeanNameUrlHandlerMapping(parserContext, source);
72-
registerHttpRequestHandlerAdapter(parserContext, source);
73-
registerSimpleControllerHandlerAdapter(parserContext, source);
74-
registerHandlerMappingIntrospector(parserContext, source);
75-
registerThemeResolver(parserContext, source);
76-
registerLocaleResolver(parserContext, source);
77-
registerFlashMapManager(parserContext, source);
78-
registerViewNameTranslator(parserContext, source);
73+
public static void registerDefaultComponents(ParserContext context, @Nullable Object source) {
74+
registerBeanNameUrlHandlerMapping(context, source);
75+
registerHttpRequestHandlerAdapter(context, source);
76+
registerSimpleControllerHandlerAdapter(context, source);
77+
registerHandlerMappingIntrospector(context, source);
78+
registerLocaleResolver(context, source);
79+
registerThemeResolver(context, source);
80+
registerViewNameTranslator(context, source);
81+
registerFlashMapManager(context, source);
7982
}
8083

8184
/**
@@ -84,21 +87,21 @@ public static void registerDefaultComponents(ParserContext parserContext, @Nulla
8487
* @return a RuntimeBeanReference to this {@link UrlPathHelper} instance
8588
*/
8689
public static RuntimeBeanReference registerUrlPathHelper(
87-
@Nullable RuntimeBeanReference urlPathHelperRef, ParserContext parserContext, @Nullable Object source) {
90+
@Nullable RuntimeBeanReference urlPathHelperRef, ParserContext context, @Nullable Object source) {
8891

8992
if (urlPathHelperRef != null) {
90-
if (parserContext.getRegistry().isAlias(URL_PATH_HELPER_BEAN_NAME)) {
91-
parserContext.getRegistry().removeAlias(URL_PATH_HELPER_BEAN_NAME);
93+
if (context.getRegistry().isAlias(URL_PATH_HELPER_BEAN_NAME)) {
94+
context.getRegistry().removeAlias(URL_PATH_HELPER_BEAN_NAME);
9295
}
93-
parserContext.getRegistry().registerAlias(urlPathHelperRef.getBeanName(), URL_PATH_HELPER_BEAN_NAME);
96+
context.getRegistry().registerAlias(urlPathHelperRef.getBeanName(), URL_PATH_HELPER_BEAN_NAME);
9497
}
95-
else if (!parserContext.getRegistry().isAlias(URL_PATH_HELPER_BEAN_NAME) &&
96-
!parserContext.getRegistry().containsBeanDefinition(URL_PATH_HELPER_BEAN_NAME)) {
98+
else if (!context.getRegistry().isAlias(URL_PATH_HELPER_BEAN_NAME) &&
99+
!context.getRegistry().containsBeanDefinition(URL_PATH_HELPER_BEAN_NAME)) {
97100
RootBeanDefinition urlPathHelperDef = new RootBeanDefinition(UrlPathHelper.class);
98101
urlPathHelperDef.setSource(source);
99102
urlPathHelperDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
100-
parserContext.getRegistry().registerBeanDefinition(URL_PATH_HELPER_BEAN_NAME, urlPathHelperDef);
101-
parserContext.registerComponent(new BeanComponentDefinition(urlPathHelperDef, URL_PATH_HELPER_BEAN_NAME));
103+
context.getRegistry().registerBeanDefinition(URL_PATH_HELPER_BEAN_NAME, urlPathHelperDef);
104+
context.registerComponent(new BeanComponentDefinition(urlPathHelperDef, URL_PATH_HELPER_BEAN_NAME));
102105
}
103106
return new RuntimeBeanReference(URL_PATH_HELPER_BEAN_NAME);
104107
}
@@ -109,21 +112,21 @@ else if (!parserContext.getRegistry().isAlias(URL_PATH_HELPER_BEAN_NAME) &&
109112
* @return a RuntimeBeanReference to this {@link PathMatcher} instance
110113
*/
111114
public static RuntimeBeanReference registerPathMatcher(@Nullable RuntimeBeanReference pathMatcherRef,
112-
ParserContext parserContext, @Nullable Object source) {
115+
ParserContext context, @Nullable Object source) {
113116

114117
if (pathMatcherRef != null) {
115-
if (parserContext.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME)) {
116-
parserContext.getRegistry().removeAlias(PATH_MATCHER_BEAN_NAME);
118+
if (context.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME)) {
119+
context.getRegistry().removeAlias(PATH_MATCHER_BEAN_NAME);
117120
}
118-
parserContext.getRegistry().registerAlias(pathMatcherRef.getBeanName(), PATH_MATCHER_BEAN_NAME);
121+
context.getRegistry().registerAlias(pathMatcherRef.getBeanName(), PATH_MATCHER_BEAN_NAME);
119122
}
120-
else if (!parserContext.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME) &&
121-
!parserContext.getRegistry().containsBeanDefinition(PATH_MATCHER_BEAN_NAME)) {
123+
else if (!context.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME) &&
124+
!context.getRegistry().containsBeanDefinition(PATH_MATCHER_BEAN_NAME)) {
122125
RootBeanDefinition pathMatcherDef = new RootBeanDefinition(AntPathMatcher.class);
123126
pathMatcherDef.setSource(source);
124127
pathMatcherDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
125-
parserContext.getRegistry().registerBeanDefinition(PATH_MATCHER_BEAN_NAME, pathMatcherDef);
126-
parserContext.registerComponent(new BeanComponentDefinition(pathMatcherDef, PATH_MATCHER_BEAN_NAME));
128+
context.getRegistry().registerBeanDefinition(PATH_MATCHER_BEAN_NAME, pathMatcherDef);
129+
context.registerComponent(new BeanComponentDefinition(pathMatcherDef, PATH_MATCHER_BEAN_NAME));
127130
}
128131
return new RuntimeBeanReference(PATH_MATCHER_BEAN_NAME);
129132
}
@@ -204,70 +207,72 @@ else if (corsConfigurations != null) {
204207
* Registers an {@link HandlerMappingIntrospector} under a well-known name
205208
* unless already registered.
206209
*/
207-
private static void registerHandlerMappingIntrospector(ParserContext parserContext, @Nullable Object source) {
208-
if (!parserContext.getRegistry().containsBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
210+
private static void registerHandlerMappingIntrospector(ParserContext context, @Nullable Object source) {
211+
if (!context.getRegistry().containsBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
209212
RootBeanDefinition beanDef = new RootBeanDefinition(HandlerMappingIntrospector.class);
210213
beanDef.setSource(source);
211214
beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
212215
beanDef.setLazyInit(true);
213-
parserContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, beanDef);
214-
parserContext.registerComponent(new BeanComponentDefinition(beanDef, HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME));
216+
context.getRegistry().registerBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, beanDef);
217+
context.registerComponent(new BeanComponentDefinition(beanDef, HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME));
215218
}
216219
}
217220

218221
/**
219-
* Registers an {@link FixedThemeResolver} under a well-known name
222+
* Registers an {@link AcceptHeaderLocaleResolver} under a well-known name
220223
* unless already registered.
221224
*/
222-
private static void registerThemeResolver(ParserContext parserContext, @Nullable Object source) {
223-
if (!parserContext.getRegistry().containsBeanDefinition(DispatcherServlet.THEME_RESOLVER_BEAN_NAME)) {
224-
RootBeanDefinition beanDef = new RootBeanDefinition(FixedThemeResolver.class);
225+
private static void registerLocaleResolver(ParserContext context, @Nullable Object source) {
226+
if (!containsBeanInHierarchy(context, DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)) {
227+
RootBeanDefinition beanDef = new RootBeanDefinition(AcceptHeaderLocaleResolver.class);
225228
beanDef.setSource(source);
226229
beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
227-
parserContext.getRegistry().registerBeanDefinition(DispatcherServlet.THEME_RESOLVER_BEAN_NAME, beanDef);
228-
parserContext.registerComponent(new BeanComponentDefinition(beanDef, DispatcherServlet.THEME_RESOLVER_BEAN_NAME));
230+
context.getRegistry().registerBeanDefinition(DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME, beanDef);
231+
context.registerComponent(new BeanComponentDefinition(beanDef, DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME));
229232
}
230233
}
231234

232235
/**
233-
* Registers an {@link AcceptHeaderLocaleResolver} under a well-known name
236+
* Registers an {@link FixedThemeResolver} under a well-known name
234237
* unless already registered.
235238
*/
236-
private static void registerLocaleResolver(ParserContext parserContext, @Nullable Object source) {
237-
if (!parserContext.getRegistry().containsBeanDefinition(DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)) {
238-
RootBeanDefinition beanDef = new RootBeanDefinition(AcceptHeaderLocaleResolver.class);
239+
private static void registerThemeResolver(ParserContext context, @Nullable Object source) {
240+
if (!containsBeanInHierarchy(context, DispatcherServlet.THEME_RESOLVER_BEAN_NAME)) {
241+
RootBeanDefinition beanDef = new RootBeanDefinition(FixedThemeResolver.class);
239242
beanDef.setSource(source);
240243
beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
241-
parserContext.getRegistry().registerBeanDefinition(DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME, beanDef);
242-
parserContext.registerComponent(new BeanComponentDefinition(beanDef, DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME));
244+
context.getRegistry().registerBeanDefinition(DispatcherServlet.THEME_RESOLVER_BEAN_NAME, beanDef);
245+
context.registerComponent(new BeanComponentDefinition(beanDef, DispatcherServlet.THEME_RESOLVER_BEAN_NAME));
243246
}
244247
}
245248

246249
/**
247-
* Registers an {@link SessionFlashMapManager} under a well-known name
250+
* Registers an {@link DefaultRequestToViewNameTranslator} under a well-known name
248251
* unless already registered.
249252
*/
250-
private static void registerFlashMapManager(ParserContext parserContext, @Nullable Object source) {
251-
if (!parserContext.getRegistry().containsBeanDefinition(DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME)) {
252-
RootBeanDefinition beanDef = new RootBeanDefinition(SessionFlashMapManager.class);
253+
private static void registerViewNameTranslator(ParserContext context, @Nullable Object source) {
254+
if (!containsBeanInHierarchy(context, DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME)) {
255+
RootBeanDefinition beanDef = new RootBeanDefinition(DefaultRequestToViewNameTranslator.class);
253256
beanDef.setSource(source);
254257
beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
255-
parserContext.getRegistry().registerBeanDefinition(DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME, beanDef);
256-
parserContext.registerComponent(new BeanComponentDefinition(beanDef, DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME));
258+
context.getRegistry().registerBeanDefinition(
259+
DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, beanDef);
260+
context.registerComponent(
261+
new BeanComponentDefinition(beanDef, DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME));
257262
}
258263
}
259264

260265
/**
261-
* Registers an {@link DefaultRequestToViewNameTranslator} under a well-known name
266+
* Registers an {@link SessionFlashMapManager} under a well-known name
262267
* unless already registered.
263268
*/
264-
private static void registerViewNameTranslator(ParserContext parserContext, @Nullable Object source) {
265-
if (!parserContext.getRegistry().containsBeanDefinition(DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME)) {
266-
RootBeanDefinition beanDef = new RootBeanDefinition(DefaultRequestToViewNameTranslator.class);
269+
private static void registerFlashMapManager(ParserContext context, @Nullable Object source) {
270+
if (!containsBeanInHierarchy(context, DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME)) {
271+
RootBeanDefinition beanDef = new RootBeanDefinition(SessionFlashMapManager.class);
267272
beanDef.setSource(source);
268273
beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
269-
parserContext.getRegistry().registerBeanDefinition(DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, beanDef);
270-
parserContext.registerComponent(new BeanComponentDefinition(beanDef, DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME));
274+
context.getRegistry().registerBeanDefinition(DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME, beanDef);
275+
context.registerComponent(new BeanComponentDefinition(beanDef, DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME));
271276
}
272277
}
273278

@@ -290,4 +295,16 @@ public static Object getContentNegotiationManager(ParserContext context) {
290295
return null;
291296
}
292297

298+
/**
299+
* Check for an existing bean of the given name, ideally in the entire
300+
* context hierarchy (through a {@code containsBean} call) since this
301+
* is also what {@code DispatcherServlet} does, or otherwise just in
302+
* the local context (through {@code containsBeanDefinition}).
303+
*/
304+
private static boolean containsBeanInHierarchy(ParserContext context, String beanName) {
305+
BeanDefinitionRegistry registry = context.getRegistry();
306+
return (registry instanceof BeanFactory ? ((BeanFactory) registry).containsBean(beanName) :
307+
registry.containsBeanDefinition(beanName));
308+
}
309+
293310
}

spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
import org.springframework.cache.CacheManager;
5151
import org.springframework.cache.concurrent.ConcurrentMapCache;
5252
import org.springframework.context.i18n.LocaleContextHolder;
53+
import org.springframework.context.support.StaticApplicationContext;
5354
import org.springframework.core.Ordered;
5455
import org.springframework.core.convert.ConversionService;
5556
import org.springframework.core.io.Resource;
@@ -100,6 +101,7 @@
100101
import org.springframework.web.servlet.handler.MappedInterceptor;
101102
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
102103
import org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor;
104+
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
103105
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
104106
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
105107
import org.springframework.web.servlet.mvc.ParameterizableViewController;
@@ -122,9 +124,12 @@
122124
import org.springframework.web.servlet.resource.ResourceUrlProviderExposingInterceptor;
123125
import org.springframework.web.servlet.resource.VersionResourceResolver;
124126
import org.springframework.web.servlet.resource.WebJarsResourceResolver;
127+
import org.springframework.web.servlet.support.SessionFlashMapManager;
128+
import org.springframework.web.servlet.theme.CookieThemeResolver;
125129
import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
126130
import org.springframework.web.servlet.view.BeanNameViewResolver;
127131
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
132+
import org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator;
128133
import org.springframework.web.servlet.view.InternalResourceView;
129134
import org.springframework.web.servlet.view.InternalResourceViewResolver;
130135
import org.springframework.web.servlet.view.RedirectView;
@@ -225,10 +230,10 @@ public void testDefaultConfig() throws Exception {
225230
assertThat(appContext.getBean(ConversionService.class)).isNotNull();
226231
assertThat(appContext.getBean(LocalValidatorFactoryBean.class)).isNotNull();
227232
assertThat(appContext.getBean(Validator.class)).isNotNull();
228-
assertThat(appContext.getBean("themeResolver", ThemeResolver.class)).isNotNull();
229233
assertThat(appContext.getBean("localeResolver", LocaleResolver.class)).isNotNull();
230-
assertThat(appContext.getBean("flashMapManager", FlashMapManager.class)).isNotNull();
234+
assertThat(appContext.getBean("themeResolver", ThemeResolver.class)).isNotNull();
231235
assertThat(appContext.getBean("viewNameTranslator", RequestToViewNameTranslator.class)).isNotNull();
236+
assertThat(appContext.getBean("flashMapManager", FlashMapManager.class)).isNotNull();
232237

233238
// default web binding initializer behavior test
234239
request = new MockHttpServletRequest("GET", "/");
@@ -262,6 +267,23 @@ public void testDefaultConfig() throws Exception {
262267
assertThat(introspector.getHandlerMappings().get(1).getClass()).isEqualTo(BeanNameUrlHandlerMapping.class);
263268
}
264269

270+
@Test // gh-25290
271+
public void testDefaultConfigWithBeansInParentContext() throws Exception {
272+
StaticApplicationContext parent = new StaticApplicationContext();
273+
parent.registerSingleton("localeResolver", CookieLocaleResolver.class);
274+
parent.registerSingleton("themeResolver", CookieThemeResolver.class);
275+
parent.registerSingleton("viewNameTranslator", DefaultRequestToViewNameTranslator.class);
276+
parent.registerSingleton("flashMapManager", SessionFlashMapManager.class);
277+
parent.refresh();
278+
appContext.setParent(parent);
279+
280+
loadBeanDefinitions("mvc-config.xml");
281+
assertThat(appContext.getBean("localeResolver")).isSameAs(parent.getBean("localeResolver"));
282+
assertThat(appContext.getBean("themeResolver")).isSameAs(parent.getBean("themeResolver"));
283+
assertThat(appContext.getBean("viewNameTranslator")).isSameAs(parent.getBean("viewNameTranslator"));
284+
assertThat(appContext.getBean("flashMapManager")).isSameAs(parent.getBean("flashMapManager"));
285+
}
286+
265287
@Test
266288
public void testCustomConversionService() throws Exception {
267289
loadBeanDefinitions("mvc-config-custom-conversion-service.xml");

0 commit comments

Comments
 (0)