Detect SpringBootServletInitializer @Configuration

Update SpringBootServletInitializer to automatically add itself as a
SpringApplication source if it is annotated with @Configuration.

Also throw a more meaningful exception if no sources have been
configured.

Fixes gh-2073
pull/2081/head
Phillip Webb 10 years ago
parent 1f66277fd6
commit 379857a111

@ -120,7 +120,6 @@ public class SpringApplicationBuilder {
* @return an application context created from the current state * @return an application context created from the current state
*/ */
public ConfigurableApplicationContext run(String... args) { public ConfigurableApplicationContext run(String... args) {
if (this.parent != null) { if (this.parent != null) {
// If there is a parent don't register a shutdown hook // If there is a parent don't register a shutdown hook
if (!this.registerShutdownHookApplied) { if (!this.registerShutdownHookApplied) {
@ -130,22 +129,26 @@ public class SpringApplicationBuilder {
initializers(new ParentContextApplicationContextInitializer( initializers(new ParentContextApplicationContextInitializer(
this.parent.run(args))); this.parent.run(args)));
} }
if (this.running.get()) { if (this.running.get()) {
// If already created we just return the existing context // If already created we just return the existing context
return this.context; return this.context;
} }
if (this.running.compareAndSet(false, true)) { if (this.running.compareAndSet(false, true)) {
synchronized (this.running) { synchronized (this.running) {
// If not already running copy the sources over and then run. // If not already running copy the sources over and then run.
this.application.setSources(this.sources); this.context = build().run(args);
this.context = this.application.run(args);
} }
} }
return this.context; return this.context;
}
/**
* Returns a fully configured {@link SpringApplication} that is ready to run.
* @return the fully configured {@link SpringApplication}.
*/
public SpringApplication build() {
this.application.setSources(this.sources);
return this.application;
} }
/** /**
@ -155,7 +158,6 @@ public class SpringApplicationBuilder {
* @return the child application builder * @return the child application builder
*/ */
public SpringApplicationBuilder child(Object... sources) { public SpringApplicationBuilder child(Object... sources) {
SpringApplicationBuilder child = new SpringApplicationBuilder(); SpringApplicationBuilder child = new SpringApplicationBuilder();
child.sources(sources); child.sources(sources);
@ -176,7 +178,6 @@ public class SpringApplicationBuilder {
this.application.setSources(this.sources); this.application.setSources(this.sources);
return child; return child;
} }
/** /**

@ -16,31 +16,45 @@
package org.springframework.boot.context.web; package org.springframework.boot.context.web;
import javax.servlet.Filter;
import javax.servlet.Servlet;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextEvent;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.builder.ParentContextApplicationContextInitializer; import org.springframework.boot.builder.ParentContextApplicationContextInitializer;
import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext;
import org.springframework.boot.context.embedded.ServletContextInitializer;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
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;
/** /**
* A handy opinionated {@link WebApplicationInitializer} for applications that starts a * An opinionated {@link WebApplicationInitializer} to run a {@link SpringApplication}
* Spring Boot application and lets it bind to the servlet and filter mappings. If your * from a traditional WAR deployment. Binds {@link Servlet}, {@link Filter} and
* application is more complicated consider using one of the other * {@link ServletContextInitializer} beans from the application context to the servlet
* WebApplicationInitializers. * container.
* <p>
* To configure the application either override the
* {@link #configure(SpringApplicationBuilder)} method (calling
* {@link SpringApplicationBuilder#sources(Object...)}) or make the initializer itself a
* {@code @Configuration}.
* <p> * <p>
* Note that a WebApplicationInitializer is only needed if you are building a war file and * Note that a WebApplicationInitializer is only needed if you are building a war file and
* deploying it. If you prefer to run an embedded container (we do) then you won't need * deploying it. If you prefer to run an embedded container then you won't need this at
* this at all. * all.
* *
* @author Dave Syer * @author Dave Syer
* @author Phillip Webb
* @see #configure(SpringApplicationBuilder)
*/ */
public abstract class SpringBootServletInitializer implements WebApplicationInitializer { public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
@ -66,29 +80,50 @@ public abstract class SpringBootServletInitializer implements WebApplicationInit
protected WebApplicationContext createRootApplicationContext( protected WebApplicationContext createRootApplicationContext(
ServletContext servletContext) { ServletContext servletContext) {
ApplicationContext parent = null; SpringApplicationBuilder builder = new SpringApplicationBuilder();
Object object = servletContext ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); if (parent != null) {
if (object instanceof ApplicationContext) {
this.logger.info("Root context already created (using as parent)."); this.logger.info("Root context already created (using as parent).");
parent = (ApplicationContext) object;
servletContext.setAttribute( servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null); WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
builder.initializers(new ParentContextApplicationContextInitializer(parent));
} }
SpringApplicationBuilder application = new SpringApplicationBuilder(); builder.initializers(new ServletContextApplicationContextInitializer(
if (parent != null) {
application.initializers(new ParentContextApplicationContextInitializer(
parent));
}
application.initializers(new ServletContextApplicationContextInitializer(
servletContext)); servletContext));
application.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class); builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
application = configure(application); builder = configure(builder);
SpringApplication application = builder.build();
if (application.getSources().isEmpty()
&& AnnotationUtils.findAnnotation(getClass(), Configuration.class) != null) {
application.getSources().add(getClass());
}
Assert.state(application.getSources().size() > 0,
"No SpringApplication sources have been defined. Either override the "
+ "configure method or add an @Configuration annotation");
// Ensure error pages are registered // Ensure error pages are registered
application.sources(ErrorPageFilter.class); application.getSources().add(ErrorPageFilter.class);
return run(application);
}
/**
* Called to run a fully configured {@link SpringApplication}.
* @param application the application to run
* @return the {@link WebApplicationContext}
*/
protected WebApplicationContext run(SpringApplication application) {
return (WebApplicationContext) application.run(); return (WebApplicationContext) application.run();
} }
private ApplicationContext getExistingRootWebApplicationContext(
ServletContext servletContext) {
Object context = servletContext
.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
if (context instanceof ApplicationContext) {
return (ApplicationContext) context;
}
return null;
}
/** /**
* Configure the application. Normally all you would need to do it add sources (e.g. * Configure the application. Normally all you would need to do it add sources (e.g.
* config classes) because other settings have sensible defaults. You might choose * config classes) because other settings have sensible defaults. You might choose

@ -0,0 +1,110 @@
/*
* Copyright 2012-2014 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.context.web;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.servlet.ServletContext;
import org.hamcrest.Matcher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Configuration;
import org.springframework.mock.web.MockServletContext;
import org.springframework.web.context.WebApplicationContext;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
/**
* Tests for {@link SpringBootServletInitializerTests}.
*
* @author Phillip Webb
*/
public class SpringBootServletInitializerTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
private ServletContext servletContext = new MockServletContext();
private SpringApplication application;
@Test
public void failsWithoutConfigure() throws Exception {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("No SpringApplication sources have been defined");
new MockSpringBootServletInitializer()
.createRootApplicationContext(this.servletContext);
}
@Test
public void withConfigurationAnnotation() throws Exception {
new WithConfigurationAnnotation()
.createRootApplicationContext(this.servletContext);
assertThat(this.application.getSources(),
equalToSet(WithConfigurationAnnotation.class, ErrorPageFilter.class));
}
@Test
public void withConfiguredSource() throws Exception {
new WithConfiguredSource().createRootApplicationContext(this.servletContext);
assertThat(this.application.getSources(),
equalToSet(Config.class, ErrorPageFilter.class));
}
private Matcher<? super Set<Object>> equalToSet(Object... items) {
Set<Object> set = new LinkedHashSet<Object>();
for (Object item : items) {
set.add(item);
}
return equalTo(set);
}
private class MockSpringBootServletInitializer extends SpringBootServletInitializer {
@Override
protected WebApplicationContext run(SpringApplication application) {
SpringBootServletInitializerTests.this.application = application;
return null;
};
}
@Configuration
public class WithConfigurationAnnotation extends MockSpringBootServletInitializer {
}
public class WithConfiguredSource extends MockSpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Config.class);
}
}
@Configuration
public static class Config {
}
}
Loading…
Cancel
Save