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
pull/9712/merge
Andy Wilkinson 7 years ago
parent 71dbbc0d66
commit 858b092a87

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

@ -37,6 +37,7 @@ import org.springframework.util.Assert;
import org.springframework.web.WebApplicationInitializer; import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener; import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.StandardServletEnvironment;
/** /**
* An opinionated {@link WebApplicationInitializer} to run a {@link SpringApplication} * An opinionated {@link WebApplicationInitializer} to run a {@link SpringApplication}
@ -103,6 +104,9 @@ public abstract class SpringBootServletInitializer implements WebApplicationInit
protected WebApplicationContext createRootApplicationContext( protected WebApplicationContext createRootApplicationContext(
ServletContext servletContext) { ServletContext servletContext) {
SpringApplicationBuilder builder = createSpringApplicationBuilder(); SpringApplicationBuilder builder = createSpringApplicationBuilder();
StandardServletEnvironment environment = new StandardServletEnvironment();
environment.initPropertySources(servletContext, null);
builder.environment(environment);
builder.main(getClass()); builder.main(getClass());
ApplicationContext parent = getExistingRootWebApplicationContext(servletContext); ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
if (parent != null) { if (parent != null) {
@ -113,7 +117,6 @@ public abstract class SpringBootServletInitializer implements WebApplicationInit
} }
builder.initializers( builder.initializers(
new ServletContextApplicationContextInitializer(servletContext)); new ServletContextApplicationContextInitializer(servletContext));
builder.listeners(new ServletContextApplicationListener(servletContext));
builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class); builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
builder = configure(builder); builder = configure(builder);
SpringApplication application = builder.build(); SpringApplication application = builder.build();

@ -16,6 +16,9 @@
package org.springframework.boot.web.support; package org.springframework.boot.web.support;
import java.util.Arrays;
import java.util.Collections;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.servlet.ServletException; 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.EmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory; 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.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.env.PropertySource;
import org.springframework.mock.web.MockServletContext; import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.StandardServletEnvironment;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/** /**
* Tests for {@link SpringBootServletInitializer}. * Tests for {@link SpringBootServletInitializer}.
@ -135,10 +144,43 @@ public class SpringBootServletInitializerTests {
} }
@Test @Test
public void servletContextApplicationListenerIsAdded() { public void servletContextPropertySourceIsAvailablePriorToRefresh()
new WithConfiguredSource().createRootApplicationContext(this.servletContext); throws ServletException {
assertThat(this.application.getListeners()) ServletContext servletContext = mock(ServletContext.class);
.hasAtLeastOneElementOfType(ServletContextApplicationListener.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.<String>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 { private class MockSpringBootServletInitializer extends SpringBootServletInitializer {
@ -221,4 +263,17 @@ public class SpringBootServletInitializerTests {
} }
private static final class PropertySourceVerifyingApplicationListener
implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
@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");
}
}
} }

Loading…
Cancel
Save