diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java index 17fdf02836..d85b2291aa 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/MvcNamespaceUtils.java @@ -19,9 +19,11 @@ package org.springframework.web.servlet.config; import java.util.LinkedHashMap; import java.util.Map; +import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.RuntimeBeanReference; import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.lang.Nullable; @@ -43,6 +45,7 @@ import org.springframework.web.util.UrlPathHelper; * Convenience methods for use in MVC namespace BeanDefinitionParsers. * * @author Rossen Stoyanchev + * @author Juergen Hoeller * @author Brian Clozel * @author Marten Deinum * @since 3.1 @@ -67,15 +70,15 @@ public abstract class MvcNamespaceUtils { private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector"; - public static void registerDefaultComponents(ParserContext parserContext, @Nullable Object source) { - registerBeanNameUrlHandlerMapping(parserContext, source); - registerHttpRequestHandlerAdapter(parserContext, source); - registerSimpleControllerHandlerAdapter(parserContext, source); - registerHandlerMappingIntrospector(parserContext, source); - registerThemeResolver(parserContext, source); - registerLocaleResolver(parserContext, source); - registerFlashMapManager(parserContext, source); - registerViewNameTranslator(parserContext, source); + public static void registerDefaultComponents(ParserContext context, @Nullable Object source) { + registerBeanNameUrlHandlerMapping(context, source); + registerHttpRequestHandlerAdapter(context, source); + registerSimpleControllerHandlerAdapter(context, source); + registerHandlerMappingIntrospector(context, source); + registerLocaleResolver(context, source); + registerThemeResolver(context, source); + registerViewNameTranslator(context, source); + registerFlashMapManager(context, source); } /** @@ -84,21 +87,21 @@ public abstract class MvcNamespaceUtils { * @return a RuntimeBeanReference to this {@link UrlPathHelper} instance */ public static RuntimeBeanReference registerUrlPathHelper( - @Nullable RuntimeBeanReference urlPathHelperRef, ParserContext parserContext, @Nullable Object source) { + @Nullable RuntimeBeanReference urlPathHelperRef, ParserContext context, @Nullable Object source) { if (urlPathHelperRef != null) { - if (parserContext.getRegistry().isAlias(URL_PATH_HELPER_BEAN_NAME)) { - parserContext.getRegistry().removeAlias(URL_PATH_HELPER_BEAN_NAME); + if (context.getRegistry().isAlias(URL_PATH_HELPER_BEAN_NAME)) { + context.getRegistry().removeAlias(URL_PATH_HELPER_BEAN_NAME); } - parserContext.getRegistry().registerAlias(urlPathHelperRef.getBeanName(), URL_PATH_HELPER_BEAN_NAME); + context.getRegistry().registerAlias(urlPathHelperRef.getBeanName(), URL_PATH_HELPER_BEAN_NAME); } - else if (!parserContext.getRegistry().isAlias(URL_PATH_HELPER_BEAN_NAME) && - !parserContext.getRegistry().containsBeanDefinition(URL_PATH_HELPER_BEAN_NAME)) { + else if (!context.getRegistry().isAlias(URL_PATH_HELPER_BEAN_NAME) && + !context.getRegistry().containsBeanDefinition(URL_PATH_HELPER_BEAN_NAME)) { RootBeanDefinition urlPathHelperDef = new RootBeanDefinition(UrlPathHelper.class); urlPathHelperDef.setSource(source); urlPathHelperDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - parserContext.getRegistry().registerBeanDefinition(URL_PATH_HELPER_BEAN_NAME, urlPathHelperDef); - parserContext.registerComponent(new BeanComponentDefinition(urlPathHelperDef, URL_PATH_HELPER_BEAN_NAME)); + context.getRegistry().registerBeanDefinition(URL_PATH_HELPER_BEAN_NAME, urlPathHelperDef); + context.registerComponent(new BeanComponentDefinition(urlPathHelperDef, URL_PATH_HELPER_BEAN_NAME)); } return new RuntimeBeanReference(URL_PATH_HELPER_BEAN_NAME); } @@ -109,21 +112,21 @@ public abstract class MvcNamespaceUtils { * @return a RuntimeBeanReference to this {@link PathMatcher} instance */ public static RuntimeBeanReference registerPathMatcher(@Nullable RuntimeBeanReference pathMatcherRef, - ParserContext parserContext, @Nullable Object source) { + ParserContext context, @Nullable Object source) { if (pathMatcherRef != null) { - if (parserContext.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME)) { - parserContext.getRegistry().removeAlias(PATH_MATCHER_BEAN_NAME); + if (context.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME)) { + context.getRegistry().removeAlias(PATH_MATCHER_BEAN_NAME); } - parserContext.getRegistry().registerAlias(pathMatcherRef.getBeanName(), PATH_MATCHER_BEAN_NAME); + context.getRegistry().registerAlias(pathMatcherRef.getBeanName(), PATH_MATCHER_BEAN_NAME); } - else if (!parserContext.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME) && - !parserContext.getRegistry().containsBeanDefinition(PATH_MATCHER_BEAN_NAME)) { + else if (!context.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME) && + !context.getRegistry().containsBeanDefinition(PATH_MATCHER_BEAN_NAME)) { RootBeanDefinition pathMatcherDef = new RootBeanDefinition(AntPathMatcher.class); pathMatcherDef.setSource(source); pathMatcherDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - parserContext.getRegistry().registerBeanDefinition(PATH_MATCHER_BEAN_NAME, pathMatcherDef); - parserContext.registerComponent(new BeanComponentDefinition(pathMatcherDef, PATH_MATCHER_BEAN_NAME)); + context.getRegistry().registerBeanDefinition(PATH_MATCHER_BEAN_NAME, pathMatcherDef); + context.registerComponent(new BeanComponentDefinition(pathMatcherDef, PATH_MATCHER_BEAN_NAME)); } return new RuntimeBeanReference(PATH_MATCHER_BEAN_NAME); } @@ -204,28 +207,14 @@ public abstract class MvcNamespaceUtils { * Registers an {@link HandlerMappingIntrospector} under a well-known name * unless already registered. */ - private static void registerHandlerMappingIntrospector(ParserContext parserContext, @Nullable Object source) { - if (!parserContext.getRegistry().containsBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) { + private static void registerHandlerMappingIntrospector(ParserContext context, @Nullable Object source) { + if (!context.getRegistry().containsBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) { RootBeanDefinition beanDef = new RootBeanDefinition(HandlerMappingIntrospector.class); beanDef.setSource(source); beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); beanDef.setLazyInit(true); - parserContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, beanDef); - parserContext.registerComponent(new BeanComponentDefinition(beanDef, HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)); - } - } - - /** - * Registers an {@link FixedThemeResolver} under a well-known name - * unless already registered. - */ - private static void registerThemeResolver(ParserContext parserContext, @Nullable Object source) { - if (!parserContext.getRegistry().containsBeanDefinition(DispatcherServlet.THEME_RESOLVER_BEAN_NAME)) { - RootBeanDefinition beanDef = new RootBeanDefinition(FixedThemeResolver.class); - beanDef.setSource(source); - beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - parserContext.getRegistry().registerBeanDefinition(DispatcherServlet.THEME_RESOLVER_BEAN_NAME, beanDef); - parserContext.registerComponent(new BeanComponentDefinition(beanDef, DispatcherServlet.THEME_RESOLVER_BEAN_NAME)); + context.getRegistry().registerBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, beanDef); + context.registerComponent(new BeanComponentDefinition(beanDef, HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)); } } @@ -233,27 +222,27 @@ public abstract class MvcNamespaceUtils { * Registers an {@link AcceptHeaderLocaleResolver} under a well-known name * unless already registered. */ - private static void registerLocaleResolver(ParserContext parserContext, @Nullable Object source) { - if (!parserContext.getRegistry().containsBeanDefinition(DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)) { + private static void registerLocaleResolver(ParserContext context, @Nullable Object source) { + if (!containsBeanInHierarchy(context, DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)) { RootBeanDefinition beanDef = new RootBeanDefinition(AcceptHeaderLocaleResolver.class); beanDef.setSource(source); beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - parserContext.getRegistry().registerBeanDefinition(DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME, beanDef); - parserContext.registerComponent(new BeanComponentDefinition(beanDef, DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)); + context.getRegistry().registerBeanDefinition(DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME, beanDef); + context.registerComponent(new BeanComponentDefinition(beanDef, DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)); } } /** - * Registers an {@link SessionFlashMapManager} under a well-known name + * Registers an {@link FixedThemeResolver} under a well-known name * unless already registered. */ - private static void registerFlashMapManager(ParserContext parserContext, @Nullable Object source) { - if (!parserContext.getRegistry().containsBeanDefinition(DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME)) { - RootBeanDefinition beanDef = new RootBeanDefinition(SessionFlashMapManager.class); + private static void registerThemeResolver(ParserContext context, @Nullable Object source) { + if (!containsBeanInHierarchy(context, DispatcherServlet.THEME_RESOLVER_BEAN_NAME)) { + RootBeanDefinition beanDef = new RootBeanDefinition(FixedThemeResolver.class); beanDef.setSource(source); beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - parserContext.getRegistry().registerBeanDefinition(DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME, beanDef); - parserContext.registerComponent(new BeanComponentDefinition(beanDef, DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME)); + context.getRegistry().registerBeanDefinition(DispatcherServlet.THEME_RESOLVER_BEAN_NAME, beanDef); + context.registerComponent(new BeanComponentDefinition(beanDef, DispatcherServlet.THEME_RESOLVER_BEAN_NAME)); } } @@ -261,13 +250,29 @@ public abstract class MvcNamespaceUtils { * Registers an {@link DefaultRequestToViewNameTranslator} under a well-known name * unless already registered. */ - private static void registerViewNameTranslator(ParserContext parserContext, @Nullable Object source) { - if (!parserContext.getRegistry().containsBeanDefinition(DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME)) { + private static void registerViewNameTranslator(ParserContext context, @Nullable Object source) { + if (!containsBeanInHierarchy(context, DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME)) { RootBeanDefinition beanDef = new RootBeanDefinition(DefaultRequestToViewNameTranslator.class); beanDef.setSource(source); beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - parserContext.getRegistry().registerBeanDefinition(DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, beanDef); - parserContext.registerComponent(new BeanComponentDefinition(beanDef, DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME)); + context.getRegistry().registerBeanDefinition( + DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, beanDef); + context.registerComponent( + new BeanComponentDefinition(beanDef, DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME)); + } + } + + /** + * Registers an {@link SessionFlashMapManager} under a well-known name + * unless already registered. + */ + private static void registerFlashMapManager(ParserContext context, @Nullable Object source) { + if (!containsBeanInHierarchy(context, DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME)) { + RootBeanDefinition beanDef = new RootBeanDefinition(SessionFlashMapManager.class); + beanDef.setSource(source); + beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); + context.getRegistry().registerBeanDefinition(DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME, beanDef); + context.registerComponent(new BeanComponentDefinition(beanDef, DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME)); } } @@ -290,4 +295,16 @@ public abstract class MvcNamespaceUtils { return null; } + /** + * Check for an existing bean of the given name, ideally in the entire + * context hierarchy (through a {@code containsBean} call) since this + * is also what {@code DispatcherServlet} does, or otherwise just in + * the local context (through {@code containsBeanDefinition}). + */ + private static boolean containsBeanInHierarchy(ParserContext context, String beanName) { + BeanDefinitionRegistry registry = context.getRegistry(); + return (registry instanceof BeanFactory ? ((BeanFactory) registry).containsBean(beanName) : + registry.containsBeanDefinition(beanName)); + } + } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java index 845b77d510..078abe5857 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java @@ -50,6 +50,7 @@ import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.concurrent.ConcurrentMapCache; import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.context.support.StaticApplicationContext; import org.springframework.core.Ordered; import org.springframework.core.convert.ConversionService; import org.springframework.core.io.Resource; @@ -100,6 +101,7 @@ import org.springframework.web.servlet.handler.HandlerMappingIntrospector; import org.springframework.web.servlet.handler.MappedInterceptor; import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; import org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor; +import org.springframework.web.servlet.i18n.CookieLocaleResolver; import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter; import org.springframework.web.servlet.mvc.ParameterizableViewController; @@ -122,9 +124,12 @@ import org.springframework.web.servlet.resource.ResourceUrlProvider; import org.springframework.web.servlet.resource.ResourceUrlProviderExposingInterceptor; import org.springframework.web.servlet.resource.VersionResourceResolver; import org.springframework.web.servlet.resource.WebJarsResourceResolver; +import org.springframework.web.servlet.support.SessionFlashMapManager; +import org.springframework.web.servlet.theme.CookieThemeResolver; import org.springframework.web.servlet.theme.ThemeChangeInterceptor; import org.springframework.web.servlet.view.BeanNameViewResolver; import org.springframework.web.servlet.view.ContentNegotiatingViewResolver; +import org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator; import org.springframework.web.servlet.view.InternalResourceView; import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.RedirectView; @@ -225,10 +230,10 @@ public class MvcNamespaceTests { assertThat(appContext.getBean(ConversionService.class)).isNotNull(); assertThat(appContext.getBean(LocalValidatorFactoryBean.class)).isNotNull(); assertThat(appContext.getBean(Validator.class)).isNotNull(); - assertThat(appContext.getBean("themeResolver", ThemeResolver.class)).isNotNull(); assertThat(appContext.getBean("localeResolver", LocaleResolver.class)).isNotNull(); - assertThat(appContext.getBean("flashMapManager", FlashMapManager.class)).isNotNull(); + assertThat(appContext.getBean("themeResolver", ThemeResolver.class)).isNotNull(); assertThat(appContext.getBean("viewNameTranslator", RequestToViewNameTranslator.class)).isNotNull(); + assertThat(appContext.getBean("flashMapManager", FlashMapManager.class)).isNotNull(); // default web binding initializer behavior test request = new MockHttpServletRequest("GET", "/"); @@ -262,6 +267,23 @@ public class MvcNamespaceTests { assertThat(introspector.getHandlerMappings().get(1).getClass()).isEqualTo(BeanNameUrlHandlerMapping.class); } + @Test // gh-25290 + public void testDefaultConfigWithBeansInParentContext() throws Exception { + StaticApplicationContext parent = new StaticApplicationContext(); + parent.registerSingleton("localeResolver", CookieLocaleResolver.class); + parent.registerSingleton("themeResolver", CookieThemeResolver.class); + parent.registerSingleton("viewNameTranslator", DefaultRequestToViewNameTranslator.class); + parent.registerSingleton("flashMapManager", SessionFlashMapManager.class); + parent.refresh(); + appContext.setParent(parent); + + loadBeanDefinitions("mvc-config.xml"); + assertThat(appContext.getBean("localeResolver")).isSameAs(parent.getBean("localeResolver")); + assertThat(appContext.getBean("themeResolver")).isSameAs(parent.getBean("themeResolver")); + assertThat(appContext.getBean("viewNameTranslator")).isSameAs(parent.getBean("viewNameTranslator")); + assertThat(appContext.getBean("flashMapManager")).isSameAs(parent.getBean("flashMapManager")); + } + @Test public void testCustomConversionService() throws Exception { loadBeanDefinitions("mvc-config-custom-conversion-service.xml");