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
*/
public ConfigurableApplicationContext run(String... args) {
if (this.parent != null) {
// If there is a parent don't register a shutdown hook
if (!this.registerShutdownHookApplied) {
@ -130,22 +129,26 @@ public class SpringApplicationBuilder {
initializers(new ParentContextApplicationContextInitializer(
this.parent.run(args)));
}
if (this.running.get()) {
// If already created we just return the existing context
return this.context;
}
if (this.running.compareAndSet(false, true)) {
synchronized (this.running) {
// If not already running copy the sources over and then run.
this.application.setSources(this.sources);
this.context = this.application.run(args);
this.context = build().run(args);
}
}
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
*/
public SpringApplicationBuilder child(Object... sources) {
SpringApplicationBuilder child = new SpringApplicationBuilder();
child.sources(sources);
@ -176,7 +178,6 @@ public class SpringApplicationBuilder {
this.application.setSources(this.sources);
return child;
}
/**

@ -16,31 +16,45 @@
package org.springframework.boot.context.web;
import javax.servlet.Filter;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.builder.ParentContextApplicationContextInitializer;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext;
import org.springframework.boot.context.embedded.ServletContextInitializer;
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.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
/**
* A handy opinionated {@link WebApplicationInitializer} for applications that starts a
* Spring Boot application and lets it bind to the servlet and filter mappings. If your
* application is more complicated consider using one of the other
* WebApplicationInitializers.
* An opinionated {@link WebApplicationInitializer} to run a {@link SpringApplication}
* from a traditional WAR deployment. Binds {@link Servlet}, {@link Filter} and
* {@link ServletContextInitializer} beans from the application context to the servlet
* 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>
* 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
* this at all.
* deploying it. If you prefer to run an embedded container then you won't need this at
* all.
*
* @author Dave Syer
* @author Phillip Webb
* @see #configure(SpringApplicationBuilder)
*/
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
@ -66,29 +80,50 @@ public abstract class SpringBootServletInitializer implements WebApplicationInit
protected WebApplicationContext createRootApplicationContext(
ServletContext servletContext) {
ApplicationContext parent = null;
Object object = servletContext
.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
if (object instanceof ApplicationContext) {
SpringApplicationBuilder builder = new SpringApplicationBuilder();
ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
parent = (ApplicationContext) object;
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
builder.initializers(new ParentContextApplicationContextInitializer(parent));
}
SpringApplicationBuilder application = new SpringApplicationBuilder();
if (parent != null) {
application.initializers(new ParentContextApplicationContextInitializer(
parent));
}
application.initializers(new ServletContextApplicationContextInitializer(
builder.initializers(new ServletContextApplicationContextInitializer(
servletContext));
application.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
application = configure(application);
builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
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
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();
}
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.
* 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