From 345c0fc5a490cc792559eab4c42cbc2e7739e729 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Fri, 20 Sep 2013 14:35:42 +0100 Subject: [PATCH] Add SpringApplicationBuilder Builder for SpringApplication and ApplicationContext instances with convenient fluent API and context hierarchy support. Simple example of a context hierarchy: new SpringApplicationBuilder(ParentConfig.class) .child(ChildConfig.class).run(args); Another common use case is setting default arguments, e.g. active Spring profiles, to set up the environment for an application: new SpringApplicationBuilder(Application.class).profiles("server") .defaultArgs("--transport=local").run(args); If your needs are simpler, consider using the static convenience methods in SpringApplication instead. [#49703716] [bs-116] Parent context for some beans maybe? --- ...dpointWebMvcChildContextConfiguration.java | 2 +- .../EndpointWebMvcAutoConfigurationTests.java | 15 +- .../DispatcherServletAutoConfiguration.java | 7 +- .../web/MultipartAutoConfigurationTests.java | 7 +- ...igurationSampleTomcatApplicationTests.java | 4 +- .../boot/SpringApplication.java | 70 +-- .../builder/SpringApplicationBuilder.java | 405 ++++++++++++++++++ .../EmbeddedWebApplicationContext.java | 12 +- ...ontextIdApplicationContextInitializer.java | 14 +- ...tContextApplicationContextInitializer.java | 54 +++ ...tContextApplicationContextInitializer.java | 58 +++ .../web/SpringBootServletInitializer.java | 15 +- .../boot/SpringApplicationBuilderTests.java | 112 +++++ .../boot/SpringApplicationTests.java | 77 ++-- 14 files changed, 731 insertions(+), 121 deletions(-) create mode 100644 spring-boot/src/main/java/org/springframework/boot/builder/SpringApplicationBuilder.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/context/initializer/ParentContextApplicationContextInitializer.java create mode 100644 spring-boot/src/main/java/org/springframework/boot/context/initializer/ServletContextApplicationContextInitializer.java create mode 100644 spring-boot/src/test/java/org/springframework/boot/SpringApplicationBuilderTests.java diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java index 474db56654..44071b5ef7 100644 --- a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java +++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcChildContextConfiguration.java @@ -40,7 +40,7 @@ import org.springframework.web.servlet.HandlerAdapter; import org.springframework.web.servlet.HandlerMapping; /** - * Configuration for triggered from {@link EndpointWebMvcAutoConfiguration} when a new + * Configuration triggered from {@link EndpointWebMvcAutoConfiguration} when a new * {@link EmbeddedServletContainer} running on a different port is required. * * @see EndpointWebMvcAutoConfiguration diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java index 63c140dae0..11c2b4730d 100644 --- a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java +++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/EndpointWebMvcAutoConfigurationTests.java @@ -24,12 +24,11 @@ import java.nio.charset.Charset; import org.junit.After; import org.junit.Test; import org.springframework.boot.TestUtils; -import org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration; -import org.springframework.boot.actuate.autoconfigure.ManagementServerPropertiesAutoConfiguration; import org.springframework.boot.actuate.endpoint.AbstractEndpoint; import org.springframework.boot.actuate.endpoint.Endpoint; import org.springframework.boot.actuate.properties.ManagementServerProperties; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; import org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; @@ -72,7 +71,7 @@ public class EndpointWebMvcAutoConfigurationTests { this.applicationContext.register(RootConfig.class, PropertyPlaceholderAutoConfiguration.class, EmbeddedServletContainerAutoConfiguration.class, - WebMvcAutoConfiguration.class, + DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class); this.applicationContext.refresh(); @@ -89,7 +88,7 @@ public class EndpointWebMvcAutoConfigurationTests { this.applicationContext.register(RootConfig.class, DifferentPortConfig.class, PropertyPlaceholderAutoConfiguration.class, EmbeddedServletContainerAutoConfiguration.class, - WebMvcAutoConfiguration.class, + DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class); this.applicationContext.refresh(); @@ -106,7 +105,7 @@ public class EndpointWebMvcAutoConfigurationTests { this.applicationContext.register(RootConfig.class, DisableConfig.class, PropertyPlaceholderAutoConfiguration.class, EmbeddedServletContainerAutoConfiguration.class, - WebMvcAutoConfiguration.class, + DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, ManagementServerPropertiesAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class); this.applicationContext.refresh(); @@ -127,7 +126,8 @@ public class EndpointWebMvcAutoConfigurationTests { ManagementServerPropertiesAutoConfiguration.class, ServerPropertiesAutoConfiguration.class, EmbeddedServletContainerAutoConfiguration.class, - WebMvcAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class); + DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, + EndpointWebMvcAutoConfiguration.class); this.applicationContext.refresh(); assertContent("/controller", 7070, "controlleroutput"); assertContent("/endpoint", 7070, null); @@ -145,7 +145,8 @@ public class EndpointWebMvcAutoConfigurationTests { ManagementServerPropertiesAutoConfiguration.class, ServerPropertiesAutoConfiguration.class, EmbeddedServletContainerAutoConfiguration.class, - WebMvcAutoConfiguration.class, EndpointWebMvcAutoConfiguration.class); + DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, + EndpointWebMvcAutoConfiguration.class); this.applicationContext.refresh(); assertContent("/controller", 8080, "controlleroutput"); assertContent("/test/endpoint", 8080, "endpointoutput"); diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/DispatcherServletAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/DispatcherServletAutoConfiguration.java index 408262e860..72fa0ace5a 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/DispatcherServletAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/DispatcherServletAutoConfiguration.java @@ -21,9 +21,11 @@ import java.util.Arrays; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.SpringBootCondition; +import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ConditionContext; import org.springframework.context.annotation.Conditional; @@ -34,8 +36,8 @@ import org.springframework.core.type.AnnotatedTypeMetadata; import org.springframework.web.servlet.DispatcherServlet; /** - * {@link EnableAutoConfiguration Auto-configuration} for an the Spring - * {@link DispatcherServlet} servlet containers. + * {@link EnableAutoConfiguration Auto-configuration} for the Spring + * {@link DispatcherServlet} where an embedded servlet container is already present. * * @author Phillip Webb * @author Dave Syer @@ -44,6 +46,7 @@ import org.springframework.web.servlet.DispatcherServlet; @Configuration @ConditionalOnWebApplication @ConditionalOnClass(DispatcherServlet.class) +@ConditionalOnBean(EmbeddedServletContainerFactory.class) @AutoConfigureAfter(EmbeddedServletContainerAutoConfiguration.class) public class DispatcherServletAutoConfiguration { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/MultipartAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/MultipartAutoConfigurationTests.java index 272d1b1748..dbb1ded3d3 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/MultipartAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/MultipartAutoConfigurationTests.java @@ -22,8 +22,6 @@ import org.junit.After; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; -import org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration; import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; @@ -68,6 +66,7 @@ public class MultipartAutoConfigurationTests { this.context = new AnnotationConfigEmbeddedWebApplicationContext( ContainerWithNothing.class, EmbeddedServletContainerAutoConfiguration.class, + DispatcherServletAutoConfiguration.class, MultipartAutoConfiguration.class); DispatcherServlet servlet = this.context.getBean(DispatcherServlet.class); assertNull(servlet.getMultipartResolver()); @@ -86,6 +85,7 @@ public class MultipartAutoConfigurationTests { this.context = new AnnotationConfigEmbeddedWebApplicationContext( ContainerWithNoMultipartJetty.class, EmbeddedServletContainerAutoConfiguration.class, + DispatcherServletAutoConfiguration.class, MultipartAutoConfiguration.class); DispatcherServlet servlet = this.context.getBean(DispatcherServlet.class); assertNull(servlet.getMultipartResolver()); @@ -114,6 +114,7 @@ public class MultipartAutoConfigurationTests { this.context = new AnnotationConfigEmbeddedWebApplicationContext( ContainerWithNoMultipartTomcat.class, EmbeddedServletContainerAutoConfiguration.class, + DispatcherServletAutoConfiguration.class, MultipartAutoConfiguration.class); DispatcherServlet servlet = this.context.getBean(DispatcherServlet.class); assertNull(servlet.getMultipartResolver()); @@ -129,6 +130,7 @@ public class MultipartAutoConfigurationTests { this.context = new AnnotationConfigEmbeddedWebApplicationContext( ContainerWithEverythingJetty.class, EmbeddedServletContainerAutoConfiguration.class, + DispatcherServletAutoConfiguration.class, MultipartAutoConfiguration.class); this.context.getBean(MultipartConfigElement.class); assertSame(this.context.getBean(DispatcherServlet.class).getMultipartResolver(), @@ -141,6 +143,7 @@ public class MultipartAutoConfigurationTests { this.context = new AnnotationConfigEmbeddedWebApplicationContext( ContainerWithEverythingTomcat.class, EmbeddedServletContainerAutoConfiguration.class, + DispatcherServletAutoConfiguration.class, MultipartAutoConfiguration.class); this.context.getBean(MultipartConfigElement.class); assertSame(this.context.getBean(DispatcherServlet.class).getMultipartResolver(), diff --git a/spring-boot-samples/spring-boot-sample-tomcat/src/test/java/org/springframework/boot/sample/tomcat/NonAutoConfigurationSampleTomcatApplicationTests.java b/spring-boot-samples/spring-boot-sample-tomcat/src/test/java/org/springframework/boot/sample/tomcat/NonAutoConfigurationSampleTomcatApplicationTests.java index 9ce1ef182d..6d08ca819d 100644 --- a/spring-boot-samples/spring-boot-sample-tomcat/src/test/java/org/springframework/boot/sample/tomcat/NonAutoConfigurationSampleTomcatApplicationTests.java +++ b/spring-boot-samples/spring-boot-sample-tomcat/src/test/java/org/springframework/boot/sample/tomcat/NonAutoConfigurationSampleTomcatApplicationTests.java @@ -27,6 +27,7 @@ import org.junit.BeforeClass; import org.junit.Test; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; +import org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration; import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration; import org.springframework.boot.sample.tomcat.service.HelloWorldService; @@ -54,7 +55,8 @@ public class NonAutoConfigurationSampleTomcatApplicationTests { @Configuration @Import({ EmbeddedServletContainerAutoConfiguration.class, - WebMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class }) + DispatcherServletAutoConfiguration.class, WebMvcAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class }) @ComponentScan(basePackageClasses = { SampleController.class, HelloWorldService.class }) public static class NonAutoConfigurationSampleTomcatApplication { diff --git a/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java b/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java index 7600288dfe..3a948af9ee 100644 --- a/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java +++ b/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java @@ -155,9 +155,7 @@ public class SpringApplication { private ConfigurableEnvironment environment; - private ApplicationContext applicationContext; - - private Class applicationContextClass; + private Class applicationContextClass; private boolean webEnvironment; @@ -245,7 +243,8 @@ public class SpringApplication { * @param args the application arguments (usually passed from a Java main method) * @return a running {@link ApplicationContext} */ - public ApplicationContext run(String... args) { + public ConfigurableApplicationContext run(String... args) { + StopWatch stopWatch = new StopWatch(); stopWatch.start(); @@ -265,18 +264,15 @@ public class SpringApplication { } // Create, load, refresh and run the ApplicationContext - ApplicationContext context = createApplicationContext(); - if (context instanceof ConfigurableApplicationContext) { - ((ConfigurableApplicationContext) context).registerShutdownHook(); - ((ConfigurableApplicationContext) context).setEnvironment(environment); - } + ConfigurableApplicationContext context = createApplicationContext(); + context.registerShutdownHook(); + context.setEnvironment(environment); postProcessApplicationContext(context); - if (context instanceof ConfigurableApplicationContext) { - applyInitializers((ConfigurableApplicationContext) context); - } + applyInitializers(context); if (this.logStartupInfo) { logStartupInfo(); } + load(context, sources.toArray(new Object[sources.size()])); refresh(context); @@ -288,6 +284,7 @@ public class SpringApplication { runCommandLineRunners(context, args); return context; + } private Set assembleSources() { @@ -310,10 +307,6 @@ public class SpringApplication { if (this.environment != null) { return this.environment; } - if (this.applicationContext != null - && this.applicationContext.getEnvironment() instanceof ConfigurableEnvironment) { - return (ConfigurableEnvironment) this.applicationContext.getEnvironment(); - } if (this.webEnvironment) { return new StandardServletEnvironment(); } @@ -398,13 +391,9 @@ public class SpringApplication { * method will respect any explicitly set application context or application context * class before falling back to a suitable default. * @return the application context (not yet refreshed) - * @see #setApplicationContext(ApplicationContext) * @see #setApplicationContextClass(Class) */ - protected ApplicationContext createApplicationContext() { - if (this.applicationContext != null) { - return this.applicationContext; - } + protected ConfigurableApplicationContext createApplicationContext() { Class contextClass = this.applicationContextClass; if (contextClass == null) { @@ -420,7 +409,8 @@ public class SpringApplication { } } - return (ApplicationContext) BeanUtils.instantiate(contextClass); + return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass); + } /** @@ -428,7 +418,7 @@ public class SpringApplication { * apply additional processing as required. * @param context the application context */ - protected void postProcessApplicationContext(ApplicationContext context) { + protected void postProcessApplicationContext(ConfigurableApplicationContext context) { if (this.webEnvironment) { if (context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext configurableContext = (ConfigurableWebApplicationContext) context; @@ -477,6 +467,10 @@ public class SpringApplication { if (context instanceof BeanDefinitionRegistry) { return (BeanDefinitionRegistry) context; } + if (context instanceof AbstractApplicationContext) { + return (BeanDefinitionRegistry) ((AbstractApplicationContext) context) + .getBeanFactory(); + } throw new IllegalStateException("Could not locate BeanDefinitionRegistry"); } @@ -628,25 +622,12 @@ public class SpringApplication { * specified defaults to {@link #DEFAULT_WEB_CONTEXT_CLASS} for web based applications * or {@link AnnotationConfigApplicationContext} for non web based applications. * @param applicationContextClass the context class to set - * @see #setApplicationContext(ApplicationContext) */ public void setApplicationContextClass( - Class applicationContextClass) { + Class applicationContextClass) { this.applicationContextClass = applicationContextClass; } - /** - * Sets a Spring {@link ApplicationContext} that will be used for the application. If - * not specified an {@link #DEFAULT_WEB_CONTEXT_CLASS} will be created for web based - * applications or an {@link AnnotationConfigApplicationContext} for non web based - * applications. - * @param applicationContext the spring application context. - * @see #setApplicationContextClass(Class) - */ - public void setApplicationContext(ApplicationContext applicationContext) { - this.applicationContext = applicationContext; - } - /** * Sets the {@link ApplicationContextInitializer} that will be applied to the Spring * {@link ApplicationContext}. Any existing initializers will be replaced. @@ -657,15 +638,6 @@ public class SpringApplication { this.initializers = new ArrayList>(initializers); } - /** - * Add {@link ApplicationContextInitializer}s to be applied to the Spring - * {@link ApplicationContext} . - * @param initializers the initializers to add - */ - public void addInitializers(ApplicationContextInitializer... initializers) { - this.initializers.addAll(Arrays.asList(initializers)); - } - /** * Returns a mutable list of the {@link ApplicationContextInitializer}s that will be * applied to the Spring {@link ApplicationContext}. @@ -682,18 +654,18 @@ public class SpringApplication { * @param args the application arguments (usually passed from a Java main method) * @return the running {@link ApplicationContext} */ - public static ApplicationContext run(Object source, String... args) { + public static ConfigurableApplicationContext run(Object source, String... args) { return run(new Object[] { source }, args); } /** * Static helper that can be used to run a {@link SpringApplication} from the - * specified sources using default settings. + * specified sources using default settings and user supplied arguments. * @param sources the sources to load * @param args the application arguments (usually passed from a Java main method) * @return the running {@link ApplicationContext} */ - public static ApplicationContext run(Object[] sources, String[] args) { + public static ConfigurableApplicationContext run(Object[] sources, String[] args) { return new SpringApplication(sources).run(args); } diff --git a/spring-boot/src/main/java/org/springframework/boot/builder/SpringApplicationBuilder.java b/spring-boot/src/main/java/org/springframework/boot/builder/SpringApplicationBuilder.java new file mode 100644 index 0000000000..bdb5c8293e --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/builder/SpringApplicationBuilder.java @@ -0,0 +1,405 @@ +/* + * Copyright 2012-2013 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.builder; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.springframework.beans.factory.support.BeanNameGenerator; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.context.initializer.ParentContextApplicationContextInitializer; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.io.ResourceLoader; +import org.springframework.util.StringUtils; + +/** + * Builder for {@link SpringApplication} and {@link ApplicationContext} instances with + * convenient fluent API and context hierarchy support. Simple example of a context + * hierarchy: + * + *
+ * new SpringApplicationBuilder(ParentConfig.class).child(ChildConfig.class).run(args);
+ * 
+ * + * Another common use case is setting default arguments, e.g. active Spring profiles, to + * set up the environment for an application: + * + *
+ * new SpringApplicationBuilder(Application.class).profiles("server")
+ * 		.defaultArgs("--transport=local").run(args);
+ * 
+ * + *

+ * If your needs are simpler, consider using the static convenience methods in + * SpringApplication instead. + * + * @author Dave Syer + * + */ +public class SpringApplicationBuilder { + + private SpringApplication application; + private ConfigurableApplicationContext context; + private SpringApplicationBuilder parent; + private AtomicBoolean running = new AtomicBoolean(false); + private Set sources = new LinkedHashSet(); + private String[] defaultArgs; + private ConfigurableEnvironment environment; + + public SpringApplicationBuilder(Object... sources) { + this.application = new SpringApplication(sources); + } + + /** + * Accessor for the current application context. + * + * @return the current application context (or null if not yet running) + */ + public ConfigurableApplicationContext context() { + return this.context; + } + + /** + * Accessor for the current application. + * + * @return the current application (never null) + */ + public SpringApplication application() { + return this.application; + } + + /** + * Create an application context (and its parent if specified) with the command line + * args provided. The parent is run first with the same arguments if has not yet been + * started. + * + * @param args the command line arguments + * @return an application context created from the current state + */ + public ConfigurableApplicationContext run(String... args) { + + if (this.parent != null) { + // If there is a parent initialize it and make sure it is added to the current + // context + addInitializers(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); + } + } + + return this.context; + + } + + /** + * Create a child application with the provided sources. Default args and environment + * are copied down into the child, but everything else is a clean sheet. + * + * @param sources the sources for the application (Spring configuration) + * @return the child application nuilder + */ + public SpringApplicationBuilder child(Object... sources) { + + SpringApplicationBuilder child = new SpringApplicationBuilder(); + child.sources(sources); + + // Copy environment stuff from parent to child + child.defaultArgs(this.defaultArgs).environment(this.environment); + child.parent = this; + + // It's not possible if embedded containers are enabled to support web contexts as + // parents because the servlets cannot be initialized at the right point in + // lifecycle. + web(false); + + // Probably not interested in multiple banners + showBanner(false); + + // Make sure sources get copied over + this.application.setSources(this.sources); + + return child; + + } + + /** + * Add a parent application with the provided sources. Default args and environment + * are copied up into the parent, but everything else is a clean sheet. + * + * @param sources the sources for the application (Spring configuration) + * @return the parent builder + */ + public SpringApplicationBuilder parent(Object... sources) { + if (this.parent == null) { + this.parent = new SpringApplicationBuilder(sources).web(false) + .defaultArgs(this.defaultArgs).environment(this.environment); + } + else { + this.parent.sources(sources); + } + return this.parent; + } + + private SpringApplicationBuilder runAndExtractParent(String... args) { + if (this.context == null) { + run(args); + } + if (this.parent != null) { + return this.parent; + } + throw new IllegalStateException( + "No parent defined yet (please use the other overloaded parent methods to set one)"); + } + + /** + * Add an already running parent context to an existing application. + * + * @param parent the parent context + * @return the current builder (not the parent) + */ + public SpringApplicationBuilder parent(ConfigurableApplicationContext parent) { + this.parent = new SpringApplicationBuilder(); + this.parent.context = parent; + this.parent.running.set(true); + addInitializers(new ParentContextApplicationContextInitializer(parent)); + return this; + } + + /** + * Create a sibling application (one with the same parent). A side effect of calling + * this method is that the current application (and its parent) are started. + * + * @param sources the sources for the application (Spring configuration) + * + * @return the new sibling builder + */ + public SpringApplicationBuilder sibling(Object... sources) { + return runAndExtractParent().child(sources); + } + + /** + * Create a sibling application (one with the same parent). A side effect of calling + * this method is that the current application (and its parent) are started if they + * are not already running. + * + * @param sources the sources for the application (Spring configuration) + * @param args the command line arguments to use when starting the current app and its + * parent + * + * @return the new sibling builder + */ + public SpringApplicationBuilder sibling(Object[] sources, String... args) { + return runAndExtractParent(args).child(sources); + } + + /** + * Explicitly set the context class to be used. + * + * @param cls the context class to use + * @return the current builder + */ + public SpringApplicationBuilder contextClass( + Class cls) { + this.application.setApplicationContextClass(cls); + return this; + } + + /** + * Add more sources to use in this application. + * + * @param sources the sources to add + * @return the current builder + */ + public SpringApplicationBuilder sources(Object... sources) { + this.sources.addAll(new LinkedHashSet(Arrays.asList(sources))); + return this; + } + + /** + * Add more sources (configuration classes and components) to this application + * + * @param sources the sources to add + * @return the current builder + */ + public SpringApplicationBuilder sources(Class... sources) { + this.sources.addAll(new LinkedHashSet(Arrays.asList(sources))); + return this; + } + + /** + * Flag to explicitly request a web or non-web environment (auto detected based on + * classpath if not set). + * + * @param webEnvironment the flag to set + * @return the current builder + */ + public SpringApplicationBuilder web(boolean webEnvironment) { + this.application.setWebEnvironment(webEnvironment); + return this; + } + + /** + * Flag to indicate the startup information should be logged. + * + * @param logStartupInfo the flag to set. Default true. + * @return the current builder + */ + public SpringApplicationBuilder logStartupInfo(boolean logStartupInfo) { + this.application.setLogStartupInfo(logStartupInfo); + return this; + } + + /** + * Flag to indicate the startup banner should be printed. + * + * @param showBanner the flag to set. Default true. + * @return the current builder + */ + public SpringApplicationBuilder showBanner(boolean showBanner) { + this.application.setShowBanner(showBanner); + return this; + } + + /** + * Fixes the main application class that is used to anchor the startup messages. + * + * @param mainApplicationClass the class to use. + * @return the current builder + */ + public SpringApplicationBuilder main(Class mainApplicationClass) { + this.application.setMainApplicationClass(mainApplicationClass); + return this; + } + + /** + * Flag to indicate that command line arguments should be added to the environment. + * + * @param addCommandLineProperties the flag to set. Default true. + * @return the current builder + */ + public SpringApplicationBuilder addCommandLineProperties( + boolean addCommandLineProperties) { + this.application.setAddCommandLineProperties(addCommandLineProperties); + return this; + } + + /** + * Default command line arguments (overridden by explicit arguments at runtime in + * {@link #run(String...)}). + * + * @param defaultArgs the args to set. + * @return the current builder + */ + public SpringApplicationBuilder defaultArgs(String... defaultArgs) { + this.defaultArgs = defaultArgs; + this.application.setDefaultArgs(defaultArgs); + if (this.parent != null) { + this.parent.defaultArgs(defaultArgs); + this.parent.environment(this.environment); + } + return this; + } + + /** + * Set the active Spring profiles for this app (and its parent and children). + * Synonymous with {@link #defaultArgs(String...) + * defaultArgs("--spring.profiles.active=[profiles]")} + * + * @param profiles the profiles to set. + * @return the current builder + */ + public SpringApplicationBuilder profiles(String... profiles) { + defaultArgs("--spring.profiles.active=" + + StringUtils.arrayToCommaDelimitedString(profiles)); + return this; + } + + /** + * Bean name generator for automatically generated bean names in the application + * context. + * + * @param beanNameGenerator the generator to set. + * @return the current builder + */ + public SpringApplicationBuilder beanNameGenerator(BeanNameGenerator beanNameGenerator) { + this.application.setBeanNameGenerator(beanNameGenerator); + return this; + } + + /** + * Environment for the application context. + * + * @param environment the environment to set. + * @return the current builder + */ + public SpringApplicationBuilder environment(ConfigurableEnvironment environment) { + this.application.setEnvironment(environment); + this.environment = environment; + return this; + } + + /** + * {@link ResourceLoader} for the application context. If a custom class loader is + * needed, this is where it would be added. + * + * @param resourceLoader the resource loader to set. + * @return the current builder + */ + public SpringApplicationBuilder resourceLoader(ResourceLoader resourceLoader) { + this.application.setResourceLoader(resourceLoader); + return this; + } + + /** + * Add some initializers to the application (applied to the {@link ApplicationContext} + * before any bean definitions are loaded). + * + * @param initializers some initializers to add + * @return the current builder + */ + public SpringApplicationBuilder initializers( + ApplicationContextInitializer... initializers) { + addInitializers(initializers); + return this; + } + + /** + * @param initializers the initializers to add + */ + private void addInitializers(ApplicationContextInitializer... initializers) { + Set> target = new LinkedHashSet>( + this.application.getInitializers()); + target.addAll(Arrays.asList(initializers)); + this.application.setInitializers(target); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/EmbeddedWebApplicationContext.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/EmbeddedWebApplicationContext.java index 30bc46b9c4..15c863886a 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/EmbeddedWebApplicationContext.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/EmbeddedWebApplicationContext.java @@ -83,6 +83,7 @@ import org.springframework.web.context.support.WebApplicationContextUtils; * {@link XmlEmbeddedWebApplicationContext} variants. * * @author Phillip Webb + * @author Dave Syer * @see AnnotationConfigEmbeddedWebApplicationContext * @see XmlEmbeddedWebApplicationContext * @see EmbeddedServletContainerFactory @@ -146,20 +147,11 @@ public class EmbeddedWebApplicationContext extends GenericWebApplicationContext } private synchronized void createEmbeddedServletContainer() { - if (this.embeddedServletContainer == null && getServletContext() == null) { + if (this.embeddedServletContainer == null) { EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory(); this.embeddedServletContainer = containerFactory .getEmbeddedServletContainer(getSelfInitializer()); } - else if (getServletContext() != null) { - try { - getSelfInitializer().onStartup(getServletContext()); - } - catch (ServletException ex) { - throw new ApplicationContextException( - "Cannot initialize servlet context", ex); - } - } WebApplicationContextUtils.registerWebApplicationScopes(getBeanFactory(), getServletContext()); WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), diff --git a/spring-boot/src/main/java/org/springframework/boot/context/initializer/ContextIdApplicationContextInitializer.java b/spring-boot/src/main/java/org/springframework/boot/context/initializer/ContextIdApplicationContextInitializer.java index 017fdd9d50..23410ede93 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/initializer/ContextIdApplicationContextInitializer.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/initializer/ContextIdApplicationContextInitializer.java @@ -49,12 +49,24 @@ import org.springframework.util.StringUtils; public class ContextIdApplicationContextInitializer implements ApplicationContextInitializer, Ordered { - private String name = "${spring.application.name:${vcap.application.name:${spring.config.name:application}}}"; + private String name; private int index = -1; private int order = Integer.MAX_VALUE - 10; + public ContextIdApplicationContextInitializer() { + this( + "${spring.application.name:${vcap.application.name:${spring.config.name:application}}}"); + } + + /** + * @param name + */ + public ContextIdApplicationContextInitializer(String name) { + this.name = name; + } + public void setOrder(int order) { this.order = order; } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/initializer/ParentContextApplicationContextInitializer.java b/spring-boot/src/main/java/org/springframework/boot/context/initializer/ParentContextApplicationContextInitializer.java new file mode 100644 index 0000000000..03de0c5190 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/initializer/ParentContextApplicationContextInitializer.java @@ -0,0 +1,54 @@ +/* + * Copyright 2010-2012 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.initializer; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.Ordered; + +/** + * {@link ApplicationContextInitializer} for setting the parent context. + * + * @author Dave Syer + */ +public class ParentContextApplicationContextInitializer implements + ApplicationContextInitializer, Ordered { + + private int order = Integer.MIN_VALUE; + + private ApplicationContext parent; + + public ParentContextApplicationContextInitializer(ApplicationContext parent) { + this.parent = parent; + } + + public void setOrder(int order) { + this.order = order; + } + + @Override + public int getOrder() { + return this.order; + } + + @Override + public void initialize(ConfigurableApplicationContext applicationContext) { + applicationContext.setParent(this.parent); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/context/initializer/ServletContextApplicationContextInitializer.java b/spring-boot/src/main/java/org/springframework/boot/context/initializer/ServletContextApplicationContextInitializer.java new file mode 100644 index 0000000000..058dedf8f7 --- /dev/null +++ b/spring-boot/src/main/java/org/springframework/boot/context/initializer/ServletContextApplicationContextInitializer.java @@ -0,0 +1,58 @@ +/* + * Copyright 2010-2012 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.initializer; + +import javax.servlet.ServletContext; + +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.core.Ordered; +import org.springframework.web.context.ConfigurableWebApplicationContext; + +/** + * {@link ApplicationContextInitializer} for setting the servlet context. + * + * @author Dave Syer + */ +public class ServletContextApplicationContextInitializer implements + ApplicationContextInitializer, Ordered { + + private int order = Integer.MIN_VALUE; + + private ServletContext servletContext; + + /** + * @param servletContext + */ + public ServletContextApplicationContextInitializer(ServletContext servletContext) { + this.servletContext = servletContext; + } + + public void setOrder(int order) { + this.order = order; + } + + @Override + public int getOrder() { + return this.order; + } + + @Override + public void initialize(ConfigurableWebApplicationContext applicationContext) { + applicationContext.setServletContext(this.servletContext); + } + +} diff --git a/spring-boot/src/main/java/org/springframework/boot/web/SpringBootServletInitializer.java b/spring-boot/src/main/java/org/springframework/boot/web/SpringBootServletInitializer.java index 829988ec04..13e62f42e2 100644 --- a/spring-boot/src/main/java/org/springframework/boot/web/SpringBootServletInitializer.java +++ b/spring-boot/src/main/java/org/springframework/boot/web/SpringBootServletInitializer.java @@ -22,8 +22,10 @@ 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.SpringApplicationBuilder; import org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; +import org.springframework.boot.context.initializer.ParentContextApplicationContextInitializer; +import org.springframework.boot.context.initializer.ServletContextApplicationContextInitializer; import org.springframework.context.ApplicationContext; import org.springframework.util.ObjectUtils; import org.springframework.web.WebApplicationInitializer; @@ -77,11 +79,12 @@ public abstract class SpringBootServletInitializer implements WebApplicationInit servletContext.setAttribute( WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null); } - SpringApplication application = new SpringApplication(getConfigClasses()); - AnnotationConfigEmbeddedWebApplicationContext context = new AnnotationConfigEmbeddedWebApplicationContext(); - context.setParent(parent); - context.setServletContext(servletContext); - application.setApplicationContext(context); + SpringApplicationBuilder application = new SpringApplicationBuilder() + .sources(getConfigClasses()); + application.initializers( + new ParentContextApplicationContextInitializer(parent), + new ServletContextApplicationContextInitializer(servletContext)); + application.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class); return (WebApplicationContext) application.run(); } diff --git a/spring-boot/src/test/java/org/springframework/boot/SpringApplicationBuilderTests.java b/spring-boot/src/test/java/org/springframework/boot/SpringApplicationBuilderTests.java new file mode 100644 index 0000000000..1a885f30ff --- /dev/null +++ b/spring-boot/src/test/java/org/springframework/boot/SpringApplicationBuilderTests.java @@ -0,0 +1,112 @@ +/* + * Copyright 2012-2013 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; + +import org.junit.After; +import org.junit.Test; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.StaticApplicationContext; + +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +/** + * @author Dave Syer + */ +public class SpringApplicationBuilderTests { + + private ConfigurableApplicationContext context; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void specificApplicationContextClass() throws Exception { + SpringApplicationBuilder application = new SpringApplicationBuilder().sources( + ExampleConfig.class).contextClass(StaticApplicationContext.class); + this.context = application.run(); + assertThat(this.context, is(instanceOf(StaticApplicationContext.class))); + } + + @Test + public void parentContextCreation() throws Exception { + SpringApplicationBuilder application = new SpringApplicationBuilder( + ChildConfig.class).contextClass(SpyApplicationContext.class); + application.parent(ExampleConfig.class); + this.context = application.run(); + verify(((SpyApplicationContext) this.context).getApplicationContext()).setParent( + any(ApplicationContext.class)); + } + + @Test + public void parentFirstCreation() throws Exception { + SpringApplicationBuilder application = new SpringApplicationBuilder( + ExampleConfig.class).child(ChildConfig.class); + application.contextClass(SpyApplicationContext.class); + this.context = application.run(); + verify(((SpyApplicationContext) this.context).getApplicationContext()).setParent( + any(ApplicationContext.class)); + } + + @Test + public void parentContextIdentical() throws Exception { + SpringApplicationBuilder application = new SpringApplicationBuilder( + ExampleConfig.class); + application.parent(ExampleConfig.class); + application.contextClass(SpyApplicationContext.class); + this.context = application.run(); + verify(((SpyApplicationContext) this.context).getApplicationContext()).setParent( + any(ApplicationContext.class)); + } + + @Configuration + static class ExampleConfig { + + } + + @Configuration + static class ChildConfig { + + } + + public static class SpyApplicationContext extends AnnotationConfigApplicationContext { + + ConfigurableApplicationContext applicationContext = spy(new AnnotationConfigApplicationContext()); + + @Override + public void setParent(ApplicationContext parent) { + this.applicationContext.setParent(parent); + } + + public ConfigurableApplicationContext getApplicationContext() { + return this.applicationContext; + } + + } +} diff --git a/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java b/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java index b6fe2ae9de..ebf099536d 100644 --- a/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java @@ -16,6 +16,7 @@ package org.springframework.boot; +import java.util.Arrays; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; @@ -47,9 +48,7 @@ import org.springframework.core.env.PropertySource; import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.ResourceLoader; -import org.springframework.mock.web.MockServletContext; import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.web.context.support.StaticWebApplicationContext; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; @@ -74,19 +73,19 @@ public class SpringApplicationTests { @Rule public ExpectedException thrown = ExpectedException.none(); - private ApplicationContext context; + private ConfigurableApplicationContext context; private Environment getEnvironment() { - if (this.context instanceof ConfigurableApplicationContext) { - return ((ConfigurableApplicationContext) this.context).getEnvironment(); + if (this.context != null) { + return this.context.getEnvironment(); } - throw new IllegalStateException("No Environment available"); + throw new IllegalStateException("Could not obtain Environment"); } @After public void close() { - if (this.context instanceof ConfigurableApplicationContext) { - ((ConfigurableApplicationContext) this.context).close(); + if (this.context != null) { + this.context.close(); } } @@ -121,15 +120,6 @@ public class SpringApplicationTests { verify(application).printBanner(); } - @Test - public void specificApplicationContext() throws Exception { - SpringApplication application = new SpringApplication(ExampleConfig.class); - ApplicationContext applicationContext = new StaticApplicationContext(); - application.setApplicationContext(applicationContext); - this.context = application.run(); - assertThat(this.context, sameInstance(applicationContext)); - } - @Test public void specificApplicationContextClass() throws Exception { SpringApplication application = new SpringApplication(ExampleConfig.class); @@ -144,12 +134,13 @@ public class SpringApplicationTests { application.setWebEnvironment(false); final AtomicReference reference = new AtomicReference(); application - .addInitializers(new ApplicationContextInitializer() { - @Override - public void initialize(ConfigurableApplicationContext context) { - reference.set(context); - } - }); + .setInitializers(Arrays + .asList(new ApplicationContextInitializer() { + @Override + public void initialize(ConfigurableApplicationContext context) { + reference.set(context); + } + })); this.context = application.run("--foo=bar"); assertThat(this.context, sameInstance(reference.get())); // Custom initializers do not switch off the defaults @@ -177,12 +168,9 @@ public class SpringApplicationTests { public void customEnvironment() throws Exception { TestSpringApplication application = new TestSpringApplication(ExampleConfig.class); application.setWebEnvironment(false); - StaticApplicationContext applicationContext = spy(new StaticApplicationContext()); ConfigurableEnvironment environment = new StandardEnvironment(); - application.setApplicationContext(applicationContext); application.setEnvironment(environment); application.run(); - verify(applicationContext).setEnvironment(environment); verify(application.getLoader()).setEnvironment(environment); } @@ -190,12 +178,9 @@ public class SpringApplicationTests { public void customResourceLoader() throws Exception { TestSpringApplication application = new TestSpringApplication(ExampleConfig.class); application.setWebEnvironment(false); - StaticApplicationContext applicationContext = spy(new StaticApplicationContext()); ResourceLoader resourceLoader = new DefaultResourceLoader(); - application.setApplicationContext(applicationContext); application.setResourceLoader(resourceLoader); - application.run(); - verify(applicationContext).setResourceLoader(resourceLoader); + this.context = application.run(); verify(application.getLoader()).setResourceLoader(resourceLoader); } @@ -204,22 +189,15 @@ public class SpringApplicationTests { ResourceLoader resourceLoader = new DefaultResourceLoader(); TestSpringApplication application = new TestSpringApplication(resourceLoader, ExampleWebConfig.class); - StaticApplicationContext applicationContext = spy(new StaticApplicationContext()); - application.setApplicationContext(applicationContext); - application.run(); - verify(applicationContext).setResourceLoader(resourceLoader); + this.context = application.run(); verify(application.getLoader()).setResourceLoader(resourceLoader); - applicationContext.close(); } @Test public void customBeanNameGenerator() throws Exception { TestSpringApplication application = new TestSpringApplication( ExampleWebConfig.class); - StaticWebApplicationContext applicationContext = spy(new StaticWebApplicationContext()); - applicationContext.setServletContext(new MockServletContext()); BeanNameGenerator beanNameGenerator = new DefaultBeanNameGenerator(); - application.setApplicationContext(applicationContext); application.setBeanNameGenerator(beanNameGenerator); this.context = application.run(); verify(application.getLoader()).setBeanNameGenerator(beanNameGenerator); @@ -342,10 +320,10 @@ public class SpringApplicationTests { @Test public void registerShutdownHook() throws Exception { SpringApplication application = new SpringApplication(ExampleConfig.class); - ConfigurableApplicationContext applicationContext = spy(new AnnotationConfigApplicationContext()); - application.setApplicationContext(applicationContext); - application.run(); - verify(applicationContext).registerShutdownHook(); + application.setApplicationContextClass(SpyApplicationContext.class); + this.context = application.run(); + SpyApplicationContext applicationContext = (SpyApplicationContext) this.context; + verify(applicationContext.getApplicationContext()).registerShutdownHook(); } private boolean hasPropertySource(ConfigurableEnvironment environment, @@ -363,6 +341,21 @@ public class SpringApplicationTests { // FIXME test config files? + public static class SpyApplicationContext extends AnnotationConfigApplicationContext { + + ConfigurableApplicationContext applicationContext = spy(new AnnotationConfigApplicationContext()); + + @Override + public void registerShutdownHook() { + this.applicationContext.registerShutdownHook(); + } + + public ConfigurableApplicationContext getApplicationContext() { + return this.applicationContext; + } + + } + private static class TestSpringApplication extends SpringApplication { private BeanDefinitionLoader loader;