From 858b092a87a272b1d0cf89ea8eda4328bb298b9a Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 2 Oct 2017 12:27:52 +0100 Subject: [PATCH] Make servlet context property source available before refresh Previously, when deploying a Spring Boot application to a container, the servlet context property source was not fully initialised until the context was refreshed. This led to a problem where a value from a property source with lower precedence would be seen during the early stages of the application starting. Once the servlet context property source had been initialized, its value for the property would then become visible effectively making it appear as if the property's value had changed during startup. This led to a specific problem with determining active profiles. If spring.profiles.active was set both in JNDI and via the servlet context both profiles would end up being active, rather than the more intuitive behaviour of the profiles made active via the servlet context overriding those made active via JNDI. This commit updates SpringBootServletInitializer so that it explicitly creates the StandardServletEnvironment and initializes its property sources using the servlet context. This is done before the application is created and run, thereby ensuring that the servlet context property source is available throughout the application's startup. Closes gh-9972 --- .../ServletContextApplicationListener.java | 56 ----------------- .../support/SpringBootServletInitializer.java | 5 +- .../SpringBootServletInitializerTests.java | 63 +++++++++++++++++-- 3 files changed, 63 insertions(+), 61 deletions(-) delete mode 100644 spring-boot/src/main/java/org/springframework/boot/web/support/ServletContextApplicationListener.java diff --git a/spring-boot/src/main/java/org/springframework/boot/web/support/ServletContextApplicationListener.java b/spring-boot/src/main/java/org/springframework/boot/web/support/ServletContextApplicationListener.java deleted file mode 100644 index 619b790708e..00000000000 --- a/spring-boot/src/main/java/org/springframework/boot/web/support/ServletContextApplicationListener.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2012-2016 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.boot.web.support; - -import javax.servlet.ServletContext; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.context.config.ConfigFileApplicationListener; -import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; -import org.springframework.context.ApplicationListener; -import org.springframework.core.Ordered; -import org.springframework.web.context.ConfigurableWebEnvironment; - -/** - * An {@link ApplicationListener} that initializes the {@link SpringApplication} using the - * {@link ServletContext}. - * - * @author Andy Wilkinson - */ -final class ServletContextApplicationListener - implements ApplicationListener, Ordered { - - private final ServletContext servletContext; - - ServletContextApplicationListener(ServletContext servletContext) { - this.servletContext = servletContext; - } - - @Override - public int getOrder() { - return ConfigFileApplicationListener.DEFAULT_ORDER - 1; - } - - @Override - public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { - if (event.getEnvironment() instanceof ConfigurableWebEnvironment) { - ((ConfigurableWebEnvironment) event.getEnvironment()) - .initPropertySources(this.servletContext, null); - } - } - -} diff --git a/spring-boot/src/main/java/org/springframework/boot/web/support/SpringBootServletInitializer.java b/spring-boot/src/main/java/org/springframework/boot/web/support/SpringBootServletInitializer.java index 717cd102c1a..1e1d809702c 100644 --- a/spring-boot/src/main/java/org/springframework/boot/web/support/SpringBootServletInitializer.java +++ b/spring-boot/src/main/java/org/springframework/boot/web/support/SpringBootServletInitializer.java @@ -37,6 +37,7 @@ import org.springframework.util.Assert; import org.springframework.web.WebApplicationInitializer; import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.StandardServletEnvironment; /** * An opinionated {@link WebApplicationInitializer} to run a {@link SpringApplication} @@ -103,6 +104,9 @@ public abstract class SpringBootServletInitializer implements WebApplicationInit protected WebApplicationContext createRootApplicationContext( ServletContext servletContext) { SpringApplicationBuilder builder = createSpringApplicationBuilder(); + StandardServletEnvironment environment = new StandardServletEnvironment(); + environment.initPropertySources(servletContext, null); + builder.environment(environment); builder.main(getClass()); ApplicationContext parent = getExistingRootWebApplicationContext(servletContext); if (parent != null) { @@ -113,7 +117,6 @@ public abstract class SpringBootServletInitializer implements WebApplicationInit } builder.initializers( new ServletContextApplicationContextInitializer(servletContext)); - builder.listeners(new ServletContextApplicationListener(servletContext)); builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class); builder = configure(builder); SpringApplication application = builder.build(); diff --git a/spring-boot/src/test/java/org/springframework/boot/web/support/SpringBootServletInitializerTests.java b/spring-boot/src/test/java/org/springframework/boot/web/support/SpringBootServletInitializerTests.java index 684ec46c535..b0afd27835d 100644 --- a/spring-boot/src/test/java/org/springframework/boot/web/support/SpringBootServletInitializerTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/web/support/SpringBootServletInitializerTests.java @@ -16,6 +16,9 @@ package org.springframework.boot.web.support; +import java.util.Arrays; +import java.util.Collections; + import javax.servlet.ServletContext; import javax.servlet.ServletException; @@ -29,15 +32,21 @@ import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.embedded.EmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory; +import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.web.servlet.ServletContextInitializer; +import org.springframework.context.ApplicationListener; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.AbstractApplicationContext; +import org.springframework.core.env.PropertySource; import org.springframework.mock.web.MockServletContext; import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.StandardServletEnvironment; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; /** * Tests for {@link SpringBootServletInitializer}. @@ -135,10 +144,43 @@ public class SpringBootServletInitializerTests { } @Test - public void servletContextApplicationListenerIsAdded() { - new WithConfiguredSource().createRootApplicationContext(this.servletContext); - assertThat(this.application.getListeners()) - .hasAtLeastOneElementOfType(ServletContextApplicationListener.class); + public void servletContextPropertySourceIsAvailablePriorToRefresh() + throws ServletException { + ServletContext servletContext = mock(ServletContext.class); + given(servletContext.getInitParameterNames()).willReturn( + Collections.enumeration(Arrays.asList("spring.profiles.active"))); + given(servletContext.getInitParameter("spring.profiles.active")) + .willReturn("from-servlet-context"); + given(servletContext.getAttributeNames()) + .willReturn(Collections.enumeration(Collections.emptyList())); + WebApplicationContext context = null; + try { + context = new PropertySourceVerifyingSpringBootServletInitializer() + .createRootApplicationContext(servletContext); + assertThat(context.getEnvironment().getActiveProfiles()) + .containsExactly("from-servlet-context"); + } + finally { + if (context instanceof ConfigurableApplicationContext) { + ((ConfigurableApplicationContext) context).close(); + } + } + } + + private static class PropertySourceVerifyingSpringBootServletInitializer + extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { + return builder.sources(TestApp.class) + .listeners(new PropertySourceVerifyingApplicationListener()); + } + + } + + @Configuration + static class TestApp { + } private class MockSpringBootServletInitializer extends SpringBootServletInitializer { @@ -221,4 +263,17 @@ public class SpringBootServletInitializerTests { } + private static final class PropertySourceVerifyingApplicationListener + implements ApplicationListener { + + @Override + public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { + PropertySource propertySource = event.getEnvironment().getPropertySources() + .get(StandardServletEnvironment.SERVLET_CONTEXT_PROPERTY_SOURCE_NAME); + assertThat(propertySource.getProperty("spring.profiles.active")) + .isEqualTo("from-servlet-context"); + } + + } + }