From bad81cef8a3a64bfc39ec23f336fa7fb9ebc814d Mon Sep 17 00:00:00 2001 From: Marten Deinum Date: Wed, 17 Jun 2020 08:56:38 +0200 Subject: [PATCH] Align DispatcherServlet defaults for Java and XML Prior to this commit some of the default strategies defined for the DispatcherServlet weren't included in the default configuration for both Java and XML configuration. The following default beans have been added to the configuration with the name as expected by the DispatcherServlet: - org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver - org.springframework.web.servlet.theme.FixedThemeResolver - org.springframework.web.servlet.support.SessionFlashMapManager - org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator Closes gh-25209 --- .../web/servlet/config/MvcNamespaceUtils.java | 68 ++++++++++++++++++- .../WebMvcConfigurationSupport.java | 28 ++++++++ .../web/servlet/config/MvcNamespaceTests.java | 11 ++- .../WebMvcConfigurationSupportTests.java | 51 ++++++++++++++ 4 files changed, 156 insertions(+), 2 deletions(-) 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 dce95a0235e..17fdf02836a 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,10 +28,15 @@ import org.springframework.lang.Nullable; import org.springframework.util.AntPathMatcher; import org.springframework.util.PathMatcher; import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping; import org.springframework.web.servlet.handler.HandlerMappingIntrospector; +import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter; import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter; +import org.springframework.web.servlet.support.SessionFlashMapManager; +import org.springframework.web.servlet.theme.FixedThemeResolver; +import org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator; import org.springframework.web.util.UrlPathHelper; /** @@ -39,6 +44,7 @@ import org.springframework.web.util.UrlPathHelper; * * @author Rossen Stoyanchev * @author Brian Clozel + * @author Marten Deinum * @since 3.1 */ public abstract class MvcNamespaceUtils { @@ -66,6 +72,10 @@ public abstract class MvcNamespaceUtils { registerHttpRequestHandlerAdapter(parserContext, source); registerSimpleControllerHandlerAdapter(parserContext, source); registerHandlerMappingIntrospector(parserContext, source); + registerThemeResolver(parserContext, source); + registerLocaleResolver(parserContext, source); + registerFlashMapManager(parserContext, source); + registerViewNameTranslator(parserContext, source); } /** @@ -205,6 +215,62 @@ public abstract class MvcNamespaceUtils { } } + /** + * 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)); + } + } + + /** + * 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)) { + 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)); + } + } + + /** + * Registers an {@link SessionFlashMapManager} 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); + 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)); + } + } + + /** + * 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)) { + 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)); + } + } + /** * Find the {@code ContentNegotiationManager} bean created by or registered * with the {@code annotation-driven} element. diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java index 89dddbb9669..c64fcc7ed8e 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupport.java @@ -74,9 +74,13 @@ import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.method.support.CompositeUriComponentsContributor; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; +import org.springframework.web.servlet.FlashMapManager; import org.springframework.web.servlet.HandlerAdapter; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.RequestToViewNameTranslator; +import org.springframework.web.servlet.ThemeResolver; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.function.support.HandlerFunctionAdapter; import org.springframework.web.servlet.function.support.RouterFunctionMapping; @@ -85,6 +89,7 @@ import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping; import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor; import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite; import org.springframework.web.servlet.handler.HandlerMappingIntrospector; +import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; import org.springframework.web.servlet.mvc.Controller; import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter; import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter; @@ -97,6 +102,9 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; import org.springframework.web.servlet.resource.ResourceUrlProvider; import org.springframework.web.servlet.resource.ResourceUrlProviderExposingInterceptor; +import org.springframework.web.servlet.support.SessionFlashMapManager; +import org.springframework.web.servlet.theme.FixedThemeResolver; +import org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator; import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.ViewResolverComposite; import org.springframework.web.util.UrlPathHelper; @@ -1099,6 +1107,26 @@ public class WebMvcConfigurationSupport implements ApplicationContextAware, Serv return new HandlerMappingIntrospector(); } + @Bean + public LocaleResolver localeResolver() { + return new AcceptHeaderLocaleResolver(); + } + + @Bean + public ThemeResolver themeResolver() { + return new FixedThemeResolver(); + } + + @Bean + public FlashMapManager flashMapManager() { + return new SessionFlashMapManager(); + } + + @Bean + public RequestToViewNameTranslator viewNameTranslator() { + return new DefaultRequestToViewNameTranslator(); + } + private static final class NoOpValidator implements Validator { 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 fff66e500a4..ed22feb0621 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -85,9 +85,13 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.support.CompositeUriComponentsContributor; import org.springframework.web.method.support.InvocableHandlerMethod; +import org.springframework.web.servlet.FlashMapManager; import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.LocaleResolver; import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.RequestToViewNameTranslator; +import org.springframework.web.servlet.ThemeResolver; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.handler.AbstractHandlerMapping; import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping; @@ -154,6 +158,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; * @author Sebastien Deleuze * @author Kazuki Shimizu * @author Sam Brannen + * @author Marten Deinum */ public class MvcNamespaceTests { @@ -221,6 +226,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("viewNameTranslator", RequestToViewNameTranslator.class)).isNotNull(); // default web binding initializer behavior test request = new MockHttpServletRequest("GET", "/"); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java index f4d1330ac32..f74629287e6 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationSupportTests.java @@ -63,13 +63,18 @@ import org.springframework.web.method.support.CompositeUriComponentsContributor; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.servlet.FlashMapManager; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.RequestToViewNameTranslator; +import org.springframework.web.servlet.ThemeResolver; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping; import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor; import org.springframework.web.servlet.handler.HandlerExceptionResolverComposite; +import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver; import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver; import org.springframework.web.servlet.mvc.method.annotation.JsonViewRequestBodyAdvice; @@ -79,7 +84,10 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; import org.springframework.web.servlet.resource.ResourceUrlProviderExposingInterceptor; +import org.springframework.web.servlet.support.SessionFlashMapManager; +import org.springframework.web.servlet.theme.FixedThemeResolver; import org.springframework.web.servlet.view.BeanNameViewResolver; +import org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator; import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.ViewResolverComposite; import org.springframework.web.testfixture.servlet.MockHttpServletRequest; @@ -90,6 +98,10 @@ import org.springframework.web.util.UrlPathHelper; import static com.fasterxml.jackson.databind.DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES; import static com.fasterxml.jackson.databind.MapperFeature.DEFAULT_VIEW_INCLUSION; import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.web.servlet.DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME; +import static org.springframework.web.servlet.DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME; +import static org.springframework.web.servlet.DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME; +import static org.springframework.web.servlet.DispatcherServlet.THEME_RESOLVER_BEAN_NAME; /** * Integration tests for {@link WebMvcConfigurationSupport} (imported via @@ -99,6 +111,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Juergen Hoeller * @author Sebastien Deleuze * @author Sam Brannen + * @author Marten Deinum */ public class WebMvcConfigurationSupportTests { @@ -310,6 +323,44 @@ public class WebMvcConfigurationSupportTests { assertThat(pathMatcher.getClass()).isEqualTo(AntPathMatcher.class); } + @Test + public void defaultLocaleResolverConfiguration() { + ApplicationContext context = initContext(WebConfig.class); + LocaleResolver localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class); + + assertThat(localeResolver).isNotNull(); + assertThat(localeResolver).isInstanceOf(AcceptHeaderLocaleResolver.class); + } + + @Test + public void defaultThemeResolverfiguration() { + ApplicationContext context = initContext(WebConfig.class); + ThemeResolver themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class); + + assertThat(themeResolver).isNotNull(); + assertThat(themeResolver).isInstanceOf(FixedThemeResolver.class); + } + + @Test + public void defaultFlashMapManagerConfiguration() { + ApplicationContext context = initContext(WebConfig.class); + FlashMapManager flashMapManager = context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class); + + assertThat(flashMapManager).isNotNull(); + assertThat(flashMapManager).isInstanceOf(SessionFlashMapManager.class); + } + + @Test + public void defaultRequestToViewNameConfiguration() throws Exception { + ApplicationContext context = initContext(WebConfig.class); + RequestToViewNameTranslator requestToViewNameTranslator; + requestToViewNameTranslator = context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, + RequestToViewNameTranslator.class); + + assertThat(requestToViewNameTranslator).isNotNull(); + assertThat(requestToViewNameTranslator).isInstanceOf(DefaultRequestToViewNameTranslator.class); + } + private ApplicationContext initContext(Class... configClasses) { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); context.setServletContext(new MockServletContext());