Detect existing DispatcherServlet strategy beans in parent context as well

Closes gh-25290
This commit is contained in:
Juergen Hoeller 2020-11-09 12:39:09 +01:00
parent 32238cc996
commit 2497d4285f
2 changed files with 98 additions and 59 deletions

View File

@ -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));
}
}

View File

@ -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");