From ef7a82756e2beb30cb1f866d17b443135c1dc801 Mon Sep 17 00:00:00 2001 From: Scott Frederick Date: Wed, 6 Jan 2021 17:19:16 -0600 Subject: [PATCH] Allow overriding ThemeResolver and FlashMapManager beans Previously, an error would occur when a user contributed a `ThemeResolver` or `FlashMapManager` bean because those beans would would not properly override the default beans provided by Spring Framework. This commit adds conditional auto-configuration of these bean types, preferring user-provided beans and falling back to Framework-provided defaults. Fixes gh-24207 --- .../web/servlet/WebMvcAutoConfiguration.java | 19 ++++- .../servlet/WebMvcAutoConfigurationTests.java | 74 ++++++++++++++++++- 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java index 9465c1a819..5dd26b78ec 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -92,8 +92,10 @@ import org.springframework.web.filter.FormContentFilter; import org.springframework.web.filter.HiddenHttpMethodFilter; import org.springframework.web.filter.RequestContextFilter; import org.springframework.web.servlet.DispatcherServlet; +import org.springframework.web.servlet.FlashMapManager; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.ThemeResolver; import org.springframework.web.servlet.View; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer; @@ -134,6 +136,7 @@ import org.springframework.web.util.pattern.PathPatternParser; * @author Kristine Jetzke * @author Bruce Brouwer * @author Artsiom Yudovin + * @author Scott Frederick * @since 2.0.0 */ @Configuration(proxyBeanMethods = false) @@ -463,6 +466,20 @@ public class WebMvcAutoConfiguration { return localeResolver; } + @Override + @Bean + @ConditionalOnMissingBean(name = DispatcherServlet.THEME_RESOLVER_BEAN_NAME) + public ThemeResolver themeResolver() { + return super.themeResolver(); + } + + @Override + @Bean + @ConditionalOnMissingBean(name = DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME) + public FlashMapManager flashMapManager() { + return super.flashMapManager(); + } + private Optional getWelcomePage() { String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations()); return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst(); diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java index f56e03bdf6..23551042fe 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -87,10 +87,13 @@ import org.springframework.web.filter.FormContentFilter; import org.springframework.web.filter.HiddenHttpMethodFilter; import org.springframework.web.filter.RequestContextFilter; import org.springframework.web.servlet.DispatcherServlet; +import org.springframework.web.servlet.FlashMap; +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.ThemeResolver; import org.springframework.web.servlet.View; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer; @@ -119,6 +122,9 @@ import org.springframework.web.servlet.resource.ResourceResolver; import org.springframework.web.servlet.resource.ResourceTransformer; import org.springframework.web.servlet.resource.VersionResourceResolver; import org.springframework.web.servlet.resource.VersionStrategy; +import org.springframework.web.servlet.support.AbstractFlashMapManager; +import org.springframework.web.servlet.support.SessionFlashMapManager; +import org.springframework.web.servlet.theme.FixedThemeResolver; import org.springframework.web.servlet.view.AbstractView; import org.springframework.web.servlet.view.ContentNegotiatingViewResolver; import org.springframework.web.util.UrlPathHelper; @@ -137,6 +143,7 @@ import static org.mockito.Mockito.mock; * @author EddĂș MelĂ©ndez * @author Kristine Jetzke * @author Artsiom Yudovin + * @author Scott Frederick */ class WebMvcAutoConfigurationTests { @@ -362,6 +369,42 @@ class WebMvcAutoConfigurationTests { }); } + @Test + void customThemeResolverWithMatchingNameReplacesDefaultThemeResolver() { + this.contextRunner.withBean("themeResolver", CustomThemeResolver.class, CustomThemeResolver::new) + .run((context) -> { + assertThat(context).hasSingleBean(ThemeResolver.class); + assertThat(context.getBean("themeResolver")).isInstanceOf(CustomThemeResolver.class); + }); + } + + @Test + void customThemeResolverWithDifferentNameDoesNotReplaceDefaultThemeResolver() { + this.contextRunner.withBean("customThemeResolver", CustomThemeResolver.class, CustomThemeResolver::new) + .run((context) -> { + assertThat(context.getBean("customThemeResolver")).isInstanceOf(CustomThemeResolver.class); + assertThat(context.getBean("themeResolver")).isInstanceOf(FixedThemeResolver.class); + }); + } + + @Test + void customFlashMapManagerWithMatchingNameReplacesDefaultFlashMapManager() { + this.contextRunner.withBean("flashMapManager", CustomFlashMapManager.class, CustomFlashMapManager::new) + .run((context) -> { + assertThat(context).hasSingleBean(FlashMapManager.class); + assertThat(context.getBean("flashMapManager")).isInstanceOf(CustomFlashMapManager.class); + }); + } + + @Test + void customFlashMapManagerWithDifferentNameDoesNotReplaceDefaultFlashMapManager() { + this.contextRunner.withBean("customFlashMapManager", CustomFlashMapManager.class, CustomFlashMapManager::new) + .run((context) -> { + assertThat(context.getBean("customFlashMapManager")).isInstanceOf(CustomFlashMapManager.class); + assertThat(context.getBean("flashMapManager")).isInstanceOf(SessionFlashMapManager.class); + }); + } + @Test void defaultDateFormat() { this.contextRunner.run((context) -> { @@ -1407,4 +1450,33 @@ class WebMvcAutoConfigurationTests { } + static class CustomThemeResolver implements ThemeResolver { + + @Override + public String resolveThemeName(HttpServletRequest request) { + return "custom"; + } + + @Override + public void setThemeName(HttpServletRequest request, HttpServletResponse response, String themeName) { + + } + + } + + static class CustomFlashMapManager extends AbstractFlashMapManager { + + @Override + protected List retrieveFlashMaps(HttpServletRequest request) { + return null; + } + + @Override + protected void updateFlashMaps(List flashMaps, HttpServletRequest request, + HttpServletResponse response) { + + } + + } + }