From ef5c8aa30433654b01dd62bbd088cb09dd0d315a Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Fri, 10 May 2013 11:51:21 +0100 Subject: [PATCH] [bs-115] Add EmbeddedServletContainerCustomizer as a callback * All instances are called before the container is started in a bean post processor * Users still have to be careful because the customizer is called very early in the ApplicationContext lifecycle (e.g. might have to do a lookup for some dependencies instead of @Autowired) [Fixes #49671463] User-hook for customizing embedded servlet container --- .../ManagementServerConfiguration.java | 3 +- .../autoconfigure/ServerConfiguration.java | 62 ++------- ...eddedContainerCustomizerConfiguration.java | 89 +++++++++++++ ...stractEmbeddedServletContainerFactory.java | 18 ++- ...onConfigEmbeddedWebApplicationContext.java | 20 ++- ...urableEmbeddedServletContainerFactory.java | 72 +++++++++++ .../embedded/EmbeddedServletContainer.java | 2 +- .../EmbeddedServletContainerCustomizer.java | 33 +++++ .../JettyEmbeddedServletContainerFactory.java | 4 +- ...TomcatEmbeddedServletContainerFactory.java | 4 +- .../main/resources/META-INF/spring.factories | 1 + .../web/WebMvcAutoConfigurationTests.java | 121 ++++++++++++++++++ ...tEmbeddedServletContainerFactoryTests.java | 20 +-- ...figEmbeddedWebApplicationContextTests.java | 95 +++++++++++++- .../MockEmbeddedServletContainerFactory.java | 39 +++--- .../EnableConfigurationPropertiesTests.java | 4 +- 16 files changed, 494 insertions(+), 93 deletions(-) create mode 100644 spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/web/EmbeddedContainerCustomizerConfiguration.java create mode 100644 spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/ConfigurableEmbeddedServletContainerFactory.java create mode 100644 spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/EmbeddedServletContainerCustomizer.java create mode 100644 spring-bootstrap/src/test/java/org/springframework/bootstrap/autoconfigure/web/WebMvcAutoConfigurationTests.java rename spring-bootstrap/src/test/java/org/springframework/bootstrap/context/{annotation => test}/EnableConfigurationPropertiesTests.java (93%) diff --git a/spring-bootstrap-actuator/src/main/java/org/springframework/bootstrap/actuate/autoconfigure/ManagementServerConfiguration.java b/spring-bootstrap-actuator/src/main/java/org/springframework/bootstrap/actuate/autoconfigure/ManagementServerConfiguration.java index 0a9d47b5cf..0b49f3db85 100644 --- a/spring-bootstrap-actuator/src/main/java/org/springframework/bootstrap/actuate/autoconfigure/ManagementServerConfiguration.java +++ b/spring-bootstrap-actuator/src/main/java/org/springframework/bootstrap/actuate/autoconfigure/ManagementServerConfiguration.java @@ -25,6 +25,7 @@ import org.springframework.bootstrap.actuate.error.ErrorEndpoint; import org.springframework.bootstrap.actuate.properties.ManagementServerProperties; import org.springframework.bootstrap.context.annotation.ConditionalOnBean; import org.springframework.bootstrap.context.embedded.AbstractEmbeddedServletContainerFactory; +import org.springframework.bootstrap.context.embedded.ConfigurableEmbeddedServletContainerFactory; import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerFactory; import org.springframework.bootstrap.context.embedded.ErrorPage; import org.springframework.bootstrap.context.embedded.jetty.JettyEmbeddedServletContainerFactory; @@ -100,7 +101,7 @@ public class ManagementServerConfiguration implements BeanPostProcessor { if (bean instanceof AbstractEmbeddedServletContainerFactory && !this.initialized) { - AbstractEmbeddedServletContainerFactory factory = (AbstractEmbeddedServletContainerFactory) bean; + ConfigurableEmbeddedServletContainerFactory factory = (ConfigurableEmbeddedServletContainerFactory) bean; factory.setPort(this.configuration.getPort()); factory.setAddress(this.configuration.getAddress()); factory.setContextPath(this.configuration.getContextPath()); diff --git a/spring-bootstrap-actuator/src/main/java/org/springframework/bootstrap/actuate/autoconfigure/ServerConfiguration.java b/spring-bootstrap-actuator/src/main/java/org/springframework/bootstrap/actuate/autoconfigure/ServerConfiguration.java index 8b6b1cda53..c755684eaa 100644 --- a/spring-bootstrap-actuator/src/main/java/org/springframework/bootstrap/actuate/autoconfigure/ServerConfiguration.java +++ b/spring-bootstrap-actuator/src/main/java/org/springframework/bootstrap/actuate/autoconfigure/ServerConfiguration.java @@ -20,17 +20,15 @@ import javax.servlet.Servlet; import org.apache.catalina.valves.AccessLogValve; import org.apache.catalina.valves.RemoteIpValve; -import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.bootstrap.actuate.error.ErrorEndpoint; import org.springframework.bootstrap.actuate.properties.ServerProperties; import org.springframework.bootstrap.actuate.properties.ServerProperties.Tomcat; import org.springframework.bootstrap.context.annotation.ConditionalOnClass; -import org.springframework.bootstrap.context.embedded.AbstractEmbeddedServletContainerFactory; -import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerFactory; +import org.springframework.bootstrap.context.embedded.ConfigurableEmbeddedServletContainerFactory; +import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerCustomizer; import org.springframework.bootstrap.context.embedded.ErrorPage; import org.springframework.bootstrap.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.context.annotation.Bean; @@ -50,66 +48,34 @@ import org.springframework.util.StringUtils; @ConditionalOnClass({ Servlet.class }) @Order(Integer.MIN_VALUE) @Import(InfoConfiguration.class) -public class ServerConfiguration implements BeanPostProcessor, BeanFactoryAware { +public class ServerConfiguration implements EmbeddedServletContainerCustomizer { + @Autowired private BeanFactory beanFactory; - // Don't do this! We don't get a callback for our own dependencies (lifecycle). - // @Autowired - // private AbstractEmbeddedServletContainerFactory factory; - - private boolean initialized = false; - @Value("${endpoints.error.path:/error}") private String errorPath = "/error"; - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - @Bean public ErrorEndpoint errorEndpoint() { return new ErrorEndpoint(); } @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) - throws BeansException { - return bean; - } - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) - throws BeansException { - - if (bean instanceof EmbeddedServletContainerFactory) { - - if (bean instanceof AbstractEmbeddedServletContainerFactory - && !this.initialized) { - - // Cannot use @Autowired because the injection happens too early - ServerProperties server = this.beanFactory - .getBean(ServerProperties.class); - - AbstractEmbeddedServletContainerFactory factory = (AbstractEmbeddedServletContainerFactory) bean; - factory.setPort(server.getPort()); - factory.setAddress(server.getAddress()); - factory.setContextPath(server.getContextPath()); - - if (factory instanceof TomcatEmbeddedServletContainerFactory) { - configureTomcat((TomcatEmbeddedServletContainerFactory) factory, - server); - } + public void customize(ConfigurableEmbeddedServletContainerFactory factory) { - factory.addErrorPages(new ErrorPage(this.errorPath)); - this.initialized = true; + // Need to do a look up here to make it lazy + ServerProperties server = this.beanFactory.getBean(ServerProperties.class); - } + factory.setPort(server.getPort()); + factory.setAddress(server.getAddress()); + factory.setContextPath(server.getContextPath()); + if (factory instanceof TomcatEmbeddedServletContainerFactory) { + configureTomcat((TomcatEmbeddedServletContainerFactory) factory, server); } - return bean; + factory.addErrorPages(new ErrorPage(this.errorPath)); } diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/web/EmbeddedContainerCustomizerConfiguration.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/web/EmbeddedContainerCustomizerConfiguration.java new file mode 100644 index 0000000000..963d692e9c --- /dev/null +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/autoconfigure/web/EmbeddedContainerCustomizerConfiguration.java @@ -0,0 +1,89 @@ +/* + * 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.bootstrap.autoconfigure.web; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.servlet.Servlet; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.bootstrap.context.annotation.ConditionalOnClass; +import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration; +import org.springframework.bootstrap.context.embedded.ConfigurableEmbeddedServletContainerFactory; +import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerCustomizer; +import org.springframework.bootstrap.context.embedded.jetty.JettyEmbeddedServletContainerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; + +/** + * {@link EnableAutoConfiguration Auto-configuration} for + * {@link JettyEmbeddedServletContainerFactory}. + * + * @author Dave Syer + */ +@Configuration +@ConditionalOnClass({ Servlet.class }) +public class EmbeddedContainerCustomizerConfiguration { + + @Autowired(required = false) + private Set customizers = new HashSet(); + + @Bean + public BeanPostProcessor embeddedContainerCustomizerBeanPostProcessor() { + return new EmbeddedContainerCustomizerBeanPostProcessor(this.customizers); + } + + private static final class EmbeddedContainerCustomizerBeanPostProcessor implements + BeanPostProcessor { + + private List customizers; + + public EmbeddedContainerCustomizerBeanPostProcessor( + Set customizers) { + final List list = new ArrayList( + customizers); + Collections.sort(list, new AnnotationAwareOrderComparator()); + this.customizers = list; + } + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + if (bean instanceof ConfigurableEmbeddedServletContainerFactory) { + ConfigurableEmbeddedServletContainerFactory factory = (ConfigurableEmbeddedServletContainerFactory) bean; + for (EmbeddedServletContainerCustomizer customizer : this.customizers) { + customizer.customize(factory); + } + } + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } + } + +} diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/AbstractEmbeddedServletContainerFactory.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/AbstractEmbeddedServletContainerFactory.java index 52906a2367..71eb02f9cd 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/AbstractEmbeddedServletContainerFactory.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/AbstractEmbeddedServletContainerFactory.java @@ -36,7 +36,7 @@ import org.springframework.util.Assert; * @since 4.0 */ public abstract class AbstractEmbeddedServletContainerFactory implements - EmbeddedServletContainerFactory { + ConfigurableEmbeddedServletContainerFactory { private final Log logger = LogFactory.getLog(getClass()); @@ -170,6 +170,7 @@ public abstract class AbstractEmbeddedServletContainerFactory implements * @see #setInitializers * @see #getInitializers */ + @Override public void addInitializers(ServletContextInitializer... initializers) { Assert.notNull(initializers, "Initializers must not be null"); this.initializers.addAll(Arrays.asList(initializers)); @@ -181,6 +182,7 @@ public abstract class AbstractEmbeddedServletContainerFactory implements * parameters. * @return the initializers */ + @Override public List getInitializers() { return this.initializers; } @@ -190,6 +192,7 @@ public abstract class AbstractEmbeddedServletContainerFactory implements * files. * @param documentRoot the document root or {@code null} if not required */ + @Override public void setDocumentRoot(File documentRoot) { this.documentRoot = documentRoot; } @@ -198,6 +201,7 @@ public abstract class AbstractEmbeddedServletContainerFactory implements * Returns the document root which will be used by the web context to serve static * files. */ + @Override public File getDocumentRoot() { return this.documentRoot; } @@ -206,6 +210,7 @@ public abstract class AbstractEmbeddedServletContainerFactory implements * Sets the error pages that will be used when handling exceptions. * @param errorPages the error pages */ + @Override public void setErrorPages(Set errorPages) { Assert.notNull(errorPages, "ErrorPages must not be null"); this.errorPages = new LinkedHashSet(errorPages); @@ -215,6 +220,7 @@ public abstract class AbstractEmbeddedServletContainerFactory implements * Adds error pages that will be used when handling exceptions. * @param errorPages the error pages */ + @Override public void addErrorPages(ErrorPage... errorPages) { Assert.notNull(this.initializers, "ErrorPages must not be null"); this.errorPages.addAll(Arrays.asList(errorPages)); @@ -224,6 +230,7 @@ public abstract class AbstractEmbeddedServletContainerFactory implements * Returns a mutable set of {@link ErrorPage}s that will be used when handling * exceptions. */ + @Override public Set getErrorPages() { return this.errorPages; } @@ -233,6 +240,7 @@ public abstract class AbstractEmbeddedServletContainerFactory implements * files from the {@link #setDocumentRoot(File) document root} will be served. * @param registerDefaultServlet if the default servlet should be registered */ + @Override public void setRegisterDefaultServlet(boolean registerDefaultServlet) { this.registerDefaultServlet = registerDefaultServlet; } @@ -243,7 +251,8 @@ public abstract class AbstractEmbeddedServletContainerFactory implements * * @return true if the JSP servlet is to be registered */ - protected boolean getRegisterJspServlet() { + @Override + public boolean isRegisterJspServlet() { return this.registerJspServlet; } @@ -253,6 +262,7 @@ public abstract class AbstractEmbeddedServletContainerFactory implements * will be served. * @param registerJspServlet if the JSP servlet should be registered */ + @Override public void setRegisterJspServlet(boolean registerJspServlet) { this.registerJspServlet = registerJspServlet; } @@ -262,7 +272,8 @@ public abstract class AbstractEmbeddedServletContainerFactory implements * * @return true if the default servlet is to be registered */ - protected boolean getRegisterDefaultServlet() { + @Override + public boolean isRegisterDefaultServlet() { return this.registerDefaultServlet; } @@ -275,6 +286,7 @@ public abstract class AbstractEmbeddedServletContainerFactory implements * * @param jspServletClassName the class name for the JSP servlet if used */ + @Override public void setJspServletClassName(String jspServletClassName) { this.jspServletClassName = jspServletClassName; } diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/AnnotationConfigEmbeddedWebApplicationContext.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/AnnotationConfigEmbeddedWebApplicationContext.java index c4ef9a6978..62a346a110 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/AnnotationConfigEmbeddedWebApplicationContext.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/AnnotationConfigEmbeddedWebApplicationContext.java @@ -16,6 +16,7 @@ package org.springframework.bootstrap.context.embedded; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; import org.springframework.context.annotation.AnnotationConfigUtils; @@ -55,6 +56,10 @@ public class AnnotationConfigEmbeddedWebApplicationContext extends private final ClassPathBeanDefinitionScanner scanner; + private Class[] annotatedClasses; + + private String[] basePackages; + /** * Create a new {@link AnnotationConfigEmbeddedWebApplicationContext} that needs to be * populated through {@link #register} calls and then manually {@linkplain #refresh @@ -149,9 +154,9 @@ public class AnnotationConfigEmbeddedWebApplicationContext extends * @see #refresh() */ public void register(Class... annotatedClasses) { + this.annotatedClasses = annotatedClasses; Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified"); - this.reader.register(annotatedClasses); } /** @@ -162,8 +167,8 @@ public class AnnotationConfigEmbeddedWebApplicationContext extends * @see #refresh() */ public void scan(String... basePackages) { + this.basePackages = basePackages; Assert.notEmpty(basePackages, "At least one base package must be specified"); - this.scanner.scan(basePackages); } @Override @@ -172,4 +177,15 @@ public class AnnotationConfigEmbeddedWebApplicationContext extends super.prepareRefresh(); } + @Override + protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { + super.postProcessBeanFactory(beanFactory); + if (this.basePackages != null && this.basePackages.length > 0) { + this.scanner.scan(this.basePackages); + } + if (this.annotatedClasses != null && this.annotatedClasses.length > 0) { + this.reader.register(this.annotatedClasses); + } + } + } diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/ConfigurableEmbeddedServletContainerFactory.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/ConfigurableEmbeddedServletContainerFactory.java new file mode 100644 index 0000000000..6be6ba538a --- /dev/null +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/ConfigurableEmbeddedServletContainerFactory.java @@ -0,0 +1,72 @@ +/* + * Copyright 2002-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.bootstrap.context.embedded; + +import java.io.File; +import java.net.InetAddress; +import java.util.List; +import java.util.Set; + +/** + * Simple interface that represents customizations to an + * {@link EmbeddedServletContainerFactory}. + * + * @author Dave Syer + * @see EmbeddedServletContainerFactory + */ +public interface ConfigurableEmbeddedServletContainerFactory extends + EmbeddedServletContainerFactory { + + void setContextPath(String contextPath); + + String getContextPath(); + + void setPort(int port); + + int getPort(); + + void setAddress(InetAddress address); + + InetAddress getAddress(); + + void setInitializers(List initializers); + + void setJspServletClassName(String jspServletClassName); + + boolean isRegisterDefaultServlet(); + + void setRegisterJspServlet(boolean registerJspServlet); + + boolean isRegisterJspServlet(); + + void setRegisterDefaultServlet(boolean registerDefaultServlet); + + Set getErrorPages(); + + void addErrorPages(ErrorPage... errorPages); + + void setErrorPages(Set errorPages); + + File getDocumentRoot(); + + void setDocumentRoot(File documentRoot); + + List getInitializers(); + + void addInitializers(ServletContextInitializer... initializers); + +} diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/EmbeddedServletContainer.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/EmbeddedServletContainer.java index 7b8b00688a..633ad8df43 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/EmbeddedServletContainer.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/EmbeddedServletContainer.java @@ -17,7 +17,7 @@ package org.springframework.bootstrap.context.embedded; /** - * Simple interface that represents a fully configure embedded servlet container (for + * Simple interface that represents a fully configured embedded servlet container (for * example Tomcat or Jetty). Allows the container to be {@link #stop() stopped}. * *

diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/EmbeddedServletContainerCustomizer.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/EmbeddedServletContainerCustomizer.java new file mode 100644 index 0000000000..2e6b7e76e0 --- /dev/null +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/EmbeddedServletContainerCustomizer.java @@ -0,0 +1,33 @@ +/* + * 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.bootstrap.context.embedded; + +/** + * Strategy interface for customizing auto-configured embedded servlet containers. Any + * beans of this type will get a callback with the container factory before the container + * itself is started, so you can set the port, address, error pages etc. Beware: will be + * called from a BeanPostProcessor (so very early in the ApplicationContext lifecycle), so + * it might be safer to lookup dependencies lazily in the enclosing BeanFactory rather + * than injecting them with @Autowired. + * + * @author Dave Syer + * + */ +public interface EmbeddedServletContainerCustomizer { + + void customize(ConfigurableEmbeddedServletContainerFactory factory); + +} diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java index 80accec06e..cf39fb774c 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/jetty/JettyEmbeddedServletContainerFactory.java @@ -110,10 +110,10 @@ public class JettyEmbeddedServletContainerFactory extends this.context.setContextPath(StringUtils.hasLength(contextPath) ? contextPath : "/"); configureDocumentRoot(this.context); - if (getRegisterDefaultServlet()) { + if (isRegisterDefaultServlet()) { addDefaultServlet(this.context); } - if (getRegisterJspServlet() + if (isRegisterJspServlet() && ClassUtils.isPresent(getJspServletClassName(), getClass() .getClassLoader())) { addJspServlet(this.context); diff --git a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java index 333ec45e83..df8de01fb7 100644 --- a/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java +++ b/spring-bootstrap/src/main/java/org/springframework/bootstrap/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.java @@ -144,10 +144,10 @@ public class TomcatEmbeddedServletContainerFactory extends if (this.resourceLoader != null) { context.setParentClassLoader(this.resourceLoader.getClassLoader()); } - if (getRegisterDefaultServlet()) { + if (isRegisterDefaultServlet()) { addDefaultServlet(context); } - if (getRegisterJspServlet() + if (isRegisterJspServlet() && ClassUtils.isPresent(getJspServletClassName(), getClass() .getClassLoader())) { addJspServlet(context); diff --git a/spring-bootstrap/src/main/resources/META-INF/spring.factories b/spring-bootstrap/src/main/resources/META-INF/spring.factories index f724eb08f6..f446639c48 100644 --- a/spring-bootstrap/src/main/resources/META-INF/spring.factories +++ b/spring-bootstrap/src/main/resources/META-INF/spring.factories @@ -4,6 +4,7 @@ org.springframework.bootstrap.autoconfigure.data.JpaRepositoriesAutoConfiguratio org.springframework.bootstrap.autoconfigure.jdbc.EmbeddedDatabaseAutoConfiguration,\ org.springframework.bootstrap.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.bootstrap.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\ +org.springframework.bootstrap.autoconfigure.web.EmbeddedContainerCustomizerConfiguration,\ org.springframework.bootstrap.autoconfigure.web.EmbeddedJettyAutoConfiguration,\ org.springframework.bootstrap.autoconfigure.web.EmbeddedTomcatAutoConfiguration,\ org.springframework.bootstrap.autoconfigure.web.WebMvcAutoConfiguration diff --git a/spring-bootstrap/src/test/java/org/springframework/bootstrap/autoconfigure/web/WebMvcAutoConfigurationTests.java b/spring-bootstrap/src/test/java/org/springframework/bootstrap/autoconfigure/web/WebMvcAutoConfigurationTests.java new file mode 100644 index 0000000000..4919495896 --- /dev/null +++ b/spring-bootstrap/src/test/java/org/springframework/bootstrap/autoconfigure/web/WebMvcAutoConfigurationTests.java @@ -0,0 +1,121 @@ +/* + * 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.bootstrap.autoconfigure.web; + +import javax.servlet.Servlet; + +import org.junit.Test; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.bootstrap.context.annotation.ConditionalOnExpression; +import org.springframework.bootstrap.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; +import org.springframework.bootstrap.context.embedded.ConfigurableEmbeddedServletContainerFactory; +import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerCustomizer; +import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerFactory; +import org.springframework.bootstrap.context.embedded.MockEmbeddedServletContainerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Component; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.verify; + +/** + * @author Dave Syer + */ +public class WebMvcAutoConfigurationTests { + + private AnnotationConfigEmbeddedWebApplicationContext context; + + @Test + public void createFromConfigClass() throws Exception { + this.context = new AnnotationConfigEmbeddedWebApplicationContext( + WebMvcAutoConfiguration.class, EmbeddedContainerConfiguration.class); + verifyContext(); + } + + @Test + public void containerHasNoServletContext() throws Exception { + this.context = new AnnotationConfigEmbeddedWebApplicationContext( + WebMvcAutoConfiguration.class, EmbeddedContainerConfiguration.class, + EnsureContainerHasNoServletContext.class); + verifyContext(); + } + + @Test + public void customizeContainerThroughCallback() throws Exception { + this.context = new AnnotationConfigEmbeddedWebApplicationContext( + WebMvcAutoConfiguration.class, EmbeddedContainerConfiguration.class, + EmbeddedContainerCustomizerConfiguration.class, + CallbackEmbeddedContainerCustomizer.class); + verifyContext(); + assertEquals(9000, getContainerFactory().getPort()); + } + + private void verifyContext() { + MockEmbeddedServletContainerFactory containerFactory = getContainerFactory(); + Servlet servlet = this.context.getBean(Servlet.class); + verify(containerFactory.getServletContext()).addServlet("dispatcherServlet", + servlet); + } + + private MockEmbeddedServletContainerFactory getContainerFactory() { + return this.context.getBean(MockEmbeddedServletContainerFactory.class); + } + + @Configuration + @ConditionalOnExpression("true") + public static class EmbeddedContainerConfiguration { + + @Bean + public EmbeddedServletContainerFactory containerFactory() { + return new MockEmbeddedServletContainerFactory(); + } + + } + + @Component + public static class EnsureContainerHasNoServletContext implements BeanPostProcessor { + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + if (bean instanceof ConfigurableEmbeddedServletContainerFactory) { + MockEmbeddedServletContainerFactory containerFactory = (MockEmbeddedServletContainerFactory) bean; + assertNull(containerFactory.getServletContext()); + } + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) { + return bean; + } + + } + + @Component + public static class CallbackEmbeddedContainerCustomizer implements + EmbeddedServletContainerCustomizer { + @Override + public void customize(ConfigurableEmbeddedServletContainerFactory factory) { + factory.setPort(9000); + } + } + +} diff --git a/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java b/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java index 13ab520346..d9f6f41132 100644 --- a/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java +++ b/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java @@ -79,7 +79,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { @Test public void startServlet() throws Exception { - AbstractEmbeddedServletContainerFactory factory = getFactory(); + ConfigurableEmbeddedServletContainerFactory factory = getFactory(); this.container = factory .getEmbdeddedServletContainer(exampleServletRegistration()); assertThat(getResponse("http://localhost:8080/hello"), equalTo("Hello World")); @@ -87,7 +87,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { @Test public void emptyServer() throws Exception { - AbstractEmbeddedServletContainerFactory factory = getFactory(); + ConfigurableEmbeddedServletContainerFactory factory = getFactory(); factory.setPort(0); this.container = factory .getEmbdeddedServletContainer(exampleServletRegistration()); @@ -98,7 +98,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { @Test public void stopServlet() throws Exception { - AbstractEmbeddedServletContainerFactory factory = getFactory(); + ConfigurableEmbeddedServletContainerFactory factory = getFactory(); this.container = factory .getEmbdeddedServletContainer(exampleServletRegistration()); this.container.stop(); @@ -109,7 +109,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { @Test @Ignore public void restartWithKeepAlive() throws Exception { - AbstractEmbeddedServletContainerFactory factory = getFactory(); + ConfigurableEmbeddedServletContainerFactory factory = getFactory(); this.container = factory .getEmbdeddedServletContainer(exampleServletRegistration()); @@ -130,7 +130,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { @Test public void startServletAndFilter() throws Exception { - AbstractEmbeddedServletContainerFactory factory = getFactory(); + ConfigurableEmbeddedServletContainerFactory factory = getFactory(); this.container = factory.getEmbdeddedServletContainer( exampleServletRegistration(), new FilterRegistrationBean( new ExampleFilter())); @@ -140,7 +140,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { @Test public void startBlocksUntilReadyToServe() throws Exception { // FIXME Assume.group(TestGroup.LONG_RUNNING); - AbstractEmbeddedServletContainerFactory factory = getFactory(); + ConfigurableEmbeddedServletContainerFactory factory = getFactory(); final Date[] date = new Date[1]; this.container = factory .getEmbdeddedServletContainer(new ServletContextInitializer() { @@ -160,7 +160,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { @Test public void specificPort() throws Exception { - AbstractEmbeddedServletContainerFactory factory = getFactory(); + ConfigurableEmbeddedServletContainerFactory factory = getFactory(); factory.setPort(8081); this.container = factory .getEmbdeddedServletContainer(exampleServletRegistration()); @@ -169,7 +169,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { @Test public void specificContextRoot() throws Exception { - AbstractEmbeddedServletContainerFactory factory = getFactory(); + ConfigurableEmbeddedServletContainerFactory factory = getFactory(); factory.setContextPath("/say"); this.container = factory .getEmbdeddedServletContainer(exampleServletRegistration()); @@ -200,7 +200,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { @Test public void doubleStop() throws Exception { - AbstractEmbeddedServletContainerFactory factory = getFactory(); + ConfigurableEmbeddedServletContainerFactory factory = getFactory(); this.container = factory .getEmbdeddedServletContainer(exampleServletRegistration()); this.container.stop(); @@ -209,7 +209,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { @Test public void multipleConfigurations() throws Exception { - AbstractEmbeddedServletContainerFactory factory = getFactory(); + ConfigurableEmbeddedServletContainerFactory factory = getFactory(); ServletContextInitializer[] initializers = new ServletContextInitializer[6]; for (int i = 0; i < initializers.length; i++) { initializers[i] = mock(ServletContextInitializer.class); diff --git a/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/embedded/AnnotationConfigEmbeddedWebApplicationContextTests.java b/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/embedded/AnnotationConfigEmbeddedWebApplicationContextTests.java index 5d8e131cbd..b979024754 100644 --- a/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/embedded/AnnotationConfigEmbeddedWebApplicationContextTests.java +++ b/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/embedded/AnnotationConfigEmbeddedWebApplicationContextTests.java @@ -17,12 +17,18 @@ package org.springframework.bootstrap.context.embedded; import javax.servlet.Servlet; +import javax.servlet.ServletContext; import org.junit.Test; -import org.springframework.bootstrap.context.embedded.AnnotationConfigEmbeddedWebApplicationContext; import org.springframework.bootstrap.context.embedded.config.ExampleEmbeddedWebApplicationConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.context.ServletContextAware; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import static org.mockito.Mockito.*; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.mockito.Mockito.verify; /** * Tests for {@link AnnotationConfigEmbeddedWebApplicationContext}. @@ -64,10 +70,95 @@ public class AnnotationConfigEmbeddedWebApplicationContextTests { verifyContext(); } + @Test + public void createAndInitializeCyclic() throws Exception { + this.context = new AnnotationConfigEmbeddedWebApplicationContext( + ServletContextAwareEmbeddedConfiguration.class); + verifyContext(); + // You can't initialize the application context and inject the servlet context + // because of a cycle - we'd like this to be not null but it never will be + assertNull(this.context.getBean(ServletContextAwareEmbeddedConfiguration.class) + .getServletContext()); + } + + @Test + public void createAndInitializeWithRoot() throws Exception { + AnnotationConfigEmbeddedWebApplicationContext parent = new AnnotationConfigEmbeddedWebApplicationContext( + EmbeddedContainerConfiguration.class); + this.context = new AnnotationConfigEmbeddedWebApplicationContext(); + this.context.register(ServletContextAwareConfiguration.class); + this.context.setParent(parent); + this.context.setServletContext(parent.getServletContext()); + this.context.refresh(); + verifyContext(); + assertNotNull(this.context.getBean(ServletContextAwareConfiguration.class) + .getServletContext()); + } + private void verifyContext() { MockEmbeddedServletContainerFactory containerFactory = this.context .getBean(MockEmbeddedServletContainerFactory.class); Servlet servlet = this.context.getBean(Servlet.class); verify(containerFactory.getServletContext()).addServlet("servlet", servlet); } + + @Configuration + @EnableWebMvc + public static class ServletContextAwareEmbeddedConfiguration implements + ServletContextAware { + + private ServletContext servletContext; + + @Bean + public EmbeddedServletContainerFactory containerFactory() { + return new MockEmbeddedServletContainerFactory(); + } + + @Bean + public Servlet servlet() { + return new MockServlet(); + } + + @Override + public void setServletContext(ServletContext servletContext) { + this.servletContext = servletContext; + } + + public ServletContext getServletContext() { + return this.servletContext; + } + + } + + @Configuration + public static class EmbeddedContainerConfiguration { + + @Bean + public EmbeddedServletContainerFactory containerFactory() { + return new MockEmbeddedServletContainerFactory(); + } + + } + + @Configuration + @EnableWebMvc + public static class ServletContextAwareConfiguration implements ServletContextAware { + + private ServletContext servletContext; + + @Bean + public Servlet servlet() { + return new MockServlet(); + } + + @Override + public void setServletContext(ServletContext servletContext) { + this.servletContext = servletContext; + } + + public ServletContext getServletContext() { + return this.servletContext; + } + + } } diff --git a/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/embedded/MockEmbeddedServletContainerFactory.java b/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/embedded/MockEmbeddedServletContainerFactory.java index a32af3fe98..bfdb4d17db 100644 --- a/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/embedded/MockEmbeddedServletContainerFactory.java +++ b/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/embedded/MockEmbeddedServletContainerFactory.java @@ -16,12 +16,6 @@ package org.springframework.bootstrap.context.embedded; -import static org.mockito.BDDMockito.given; -import static org.mockito.Matchers.anyObject; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; - import java.util.ArrayList; import java.util.Enumeration; import java.util.List; @@ -29,6 +23,7 @@ import java.util.NoSuchElementException; import javax.servlet.Filter; import javax.servlet.FilterRegistration; +import javax.servlet.RequestDispatcher; import javax.servlet.Servlet; import javax.servlet.ServletContext; import javax.servlet.ServletException; @@ -36,25 +31,27 @@ import javax.servlet.ServletRegistration; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import org.springframework.bootstrap.context.embedded.EmbeddedServletContainer; -import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerFactory; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; /** * Mock {@link EmbeddedServletContainerFactory}. * * @author Phillip Webb */ -public class MockEmbeddedServletContainerFactory implements - EmbeddedServletContainerFactory { +public class MockEmbeddedServletContainerFactory extends + AbstractEmbeddedServletContainerFactory { private MockEmbeddedServletContainer container; - private int port; - @Override public EmbeddedServletContainer getEmbdeddedServletContainer( ServletContextInitializer... initializers) { - this.container = spy(new MockEmbeddedServletContainer(initializers, port)); + this.container = spy(new MockEmbeddedServletContainer(initializers, getPort())); return this.container; } @@ -63,19 +60,17 @@ public class MockEmbeddedServletContainerFactory implements } public ServletContext getServletContext() { - return getContainer().servletContext; + return getContainer() == null ? null : getContainer().servletContext; } public RegisteredServlet getRegisteredServlet(int index) { - return getContainer().getRegisteredServlets().get(index); + return getContainer() == null ? null : getContainer().getRegisteredServlets() + .get(index); } public RegisteredFilter getRegisteredFilter(int index) { - return getContainer().getRegisteredFilters().get(index); - } - - public void setPort(int port) { - this.port = port; + return getContainer() == null ? null : getContainer().getRegisteredFilters().get( + index); } public static class MockEmbeddedServletContainer implements EmbeddedServletContainer { @@ -128,6 +123,8 @@ public class MockEmbeddedServletContainerFactory implements MockEmbeddedServletContainer. emptyEnumeration()); given(this.servletContext.getAttributeNames()).willReturn( MockEmbeddedServletContainer. emptyEnumeration()); + given(this.servletContext.getNamedDispatcher("default")).willReturn( + mock(RequestDispatcher.class)); for (ServletContextInitializer initializer : this.initializers) { initializer.onStartup(this.servletContext); } @@ -178,7 +175,7 @@ public class MockEmbeddedServletContainerFactory implements } public int getPort() { - return port; + return this.port; } } diff --git a/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/annotation/EnableConfigurationPropertiesTests.java b/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/test/EnableConfigurationPropertiesTests.java similarity index 93% rename from spring-bootstrap/src/test/java/org/springframework/bootstrap/context/annotation/EnableConfigurationPropertiesTests.java rename to spring-bootstrap/src/test/java/org/springframework/bootstrap/context/test/EnableConfigurationPropertiesTests.java index 896a248dbc..e6ed05e4a9 100644 --- a/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/annotation/EnableConfigurationPropertiesTests.java +++ b/spring-bootstrap/src/test/java/org/springframework/bootstrap/context/test/EnableConfigurationPropertiesTests.java @@ -13,13 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springframework.bootstrap.context.annotation; +package org.springframework.bootstrap.context.test; import java.util.Properties; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.springframework.bootstrap.context.annotation.ConfigurationProperties; +import org.springframework.bootstrap.context.annotation.EnableConfigurationProperties; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;