Add EmbeddedServletContainer.start() method

Add EmbeddedServletContainer.start() method that is used to start the
embedded servlet container once the application context has finished
being refreshed.

Refactored existing EmbeddedServletContainerFactory implementations to
no longer keep server instances locally.

Issue: #53538787
This commit is contained in:
Phillip Webb 2013-07-18 11:38:25 -07:00
parent 5c11d273cc
commit 9ce3428597
10 changed files with 117 additions and 159 deletions

View File

@ -31,8 +31,6 @@ import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.util.Assert;
/**
@ -42,8 +40,7 @@ import org.springframework.util.Assert;
* @author Dave Syer
*/
public abstract class AbstractEmbeddedServletContainerFactory implements
ConfigurableEmbeddedServletContainerFactory,
ApplicationListener<ContextRefreshedEvent> {
ConfigurableEmbeddedServletContainerFactory {
private static final String[] COMMON_DOC_ROOTS = { "src/main/webapp", "public",
"static" };
@ -97,14 +94,6 @@ public abstract class AbstractEmbeddedServletContainerFactory implements
this.port = port;
}
/**
* Subclasses should use this event to tell the server to start listening for
* connections.
*/
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
}
/**
* Sets the context path for the embedded servlet container. The context should start
* with a "/" character but not end with a "/" character. The default context path can

View File

@ -35,12 +35,23 @@ public interface EmbeddedServletContainer {
*/
public static final EmbeddedServletContainer NONE = new EmbeddedServletContainer() {
public void start() throws EmbeddedServletContainerException {
// Do nothing
};
@Override
public void stop() throws EmbeddedServletContainerException {
// Do nothing
}
};
/**
* Starts the embedded servlet container. Calling this method on an already started
* container has no effect.
* @throws EmbeddedServletContainerException of the container cannot be stopped
*/
void start() throws EmbeddedServletContainerException;
/**
* Stops the embedded servlet container. Calling this method on an already stopped
* container has no effect.

View File

@ -16,6 +16,7 @@
package org.springframework.bootstrap.context.embedded;
import org.apache.catalina.core.ApplicationContext;
import org.springframework.bootstrap.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
import org.springframework.bootstrap.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
@ -33,8 +34,10 @@ import org.springframework.bootstrap.context.embedded.tomcat.TomcatEmbeddedServl
public interface EmbeddedServletContainerFactory {
/**
* Gets a new fully configured and started {@link EmbeddedServletContainer}
* instance,blocking until the point that clients can connect.
* Gets a new fully configured but paused {@link EmbeddedServletContainer} instance.
* Clients should not be able to connect to the returned server until
* {@link EmbeddedServletContainer#start()} is called (which happens when the
* {@link ApplicationContext} has been fully refreshed).
* @param initializers {@link ServletContextInitializer}s that should be applied as
* the container starts
* @return a fully configured and started {@link EmbeddedServletContainer}

View File

@ -117,7 +117,13 @@ public class EmbeddedWebApplicationContext extends GenericWebApplicationContext
protected void onRefresh() {
super.onRefresh();
registerShutdownHook();
createAndStartEmbeddedServletContainer();
createEmbeddedServletContainer();
}
@Override
protected void finishRefresh() {
super.finishRefresh();
startEmbeddedServletContainer();
}
@Override
@ -126,7 +132,7 @@ public class EmbeddedWebApplicationContext extends GenericWebApplicationContext
stopAndReleaseEmbeddedServletContainer();
}
private synchronized void createAndStartEmbeddedServletContainer() {
private synchronized void createEmbeddedServletContainer() {
if (this.embeddedServletContainer == null && getServletContext() == null) {
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
this.embeddedServletContainer = containerFactory
@ -308,6 +314,12 @@ public class EmbeddedWebApplicationContext extends GenericWebApplicationContext
return beans;
}
private void startEmbeddedServletContainer() {
if (this.embeddedServletContainer != null) {
this.embeddedServletContainer.start();
}
}
private synchronized void stopAndReleaseEmbeddedServletContainer() {
if (this.embeddedServletContainer != null) {
try {

View File

@ -41,10 +41,10 @@ public class JettyEmbeddedServletContainer implements EmbeddedServletContainer {
public JettyEmbeddedServletContainer(Server server) {
Assert.notNull(server, "Jetty Server must not be null");
this.server = server;
start();
initialize();
}
private synchronized void start() {
private synchronized void initialize() {
try {
this.server.start();
// Start the server so the ServletContext is available, but stop the
@ -61,6 +61,21 @@ public class JettyEmbeddedServletContainer implements EmbeddedServletContainer {
}
}
@Override
public void start() throws EmbeddedServletContainerException {
try {
this.server.start();
Connector[] connectors = this.server.getConnectors();
for (Connector connector : connectors) {
connector.start();
}
}
catch (Exception ex) {
throw new EmbeddedServletContainerException(
"Unable to start embedded Jetty servlet container", ex);
}
}
@Override
public synchronized void stop() {
try {

View File

@ -23,7 +23,6 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.servlet.ErrorPageErrorHandler;
@ -35,13 +34,10 @@ import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.WebAppContext;
import org.springframework.bootstrap.context.embedded.AbstractEmbeddedServletContainerFactory;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainer;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerException;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.bootstrap.context.embedded.ErrorPage;
import org.springframework.bootstrap.context.embedded.ServletContextInitializer;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@ -63,15 +59,12 @@ import org.springframework.util.StringUtils;
* @see JettyEmbeddedServletContainer
*/
public class JettyEmbeddedServletContainerFactory extends
AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware,
ApplicationListener<ContextRefreshedEvent> {
AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
private List<Configuration> configurations = new ArrayList<Configuration>();
private ResourceLoader resourceLoader;
private WebAppContext context = new WebAppContext();
/**
* Create a new {@link JettyEmbeddedServletContainerFactory} instance.
*/
@ -98,53 +91,37 @@ public class JettyEmbeddedServletContainerFactory extends
super(contextPath, port);
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (this.context != null && this.context.getServer() != null) {
try {
Connector[] connectors = this.context.getServer().getConnectors();
for (Connector connector : connectors) {
connector.start();
}
}
catch (Exception ex) {
throw new EmbeddedServletContainerException(
"Unable to start embedded Jetty servlet container", ex);
}
}
}
@Override
public EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers) {
WebAppContext context = new WebAppContext();
if (getPort() == 0) {
return EmbeddedServletContainer.NONE;
}
Server server = new Server(new InetSocketAddress(getAddress(), getPort()));
if (this.resourceLoader != null) {
this.context.setClassLoader(this.resourceLoader.getClassLoader());
context.setClassLoader(this.resourceLoader.getClassLoader());
}
String contextPath = getContextPath();
this.context.setContextPath(StringUtils.hasLength(contextPath) ? contextPath
: "/");
configureDocumentRoot(this.context);
context.setContextPath(StringUtils.hasLength(contextPath) ? contextPath : "/");
configureDocumentRoot(context);
if (isRegisterDefaultServlet()) {
addDefaultServlet(this.context);
addDefaultServlet(context);
}
if (isRegisterJspServlet()
&& ClassUtils.isPresent(getJspServletClassName(), getClass()
.getClassLoader())) {
addJspServlet(this.context);
addJspServlet(context);
}
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
Configuration[] configurations = getWebAppContextConfigurations(this.context,
Configuration[] configurations = getWebAppContextConfigurations(context,
initializersToUse);
this.context.setConfigurations(configurations);
postProcessWebAppContext(this.context);
context.setConfigurations(configurations);
postProcessWebAppContext(context);
server.setHandler(this.context);
server.setHandler(context);
return getJettyEmbeddedServletContainer(server);
}

View File

@ -17,7 +17,10 @@
package org.springframework.bootstrap.context.embedded.tomcat;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainer;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerException;
import org.springframework.util.Assert;
@ -29,11 +32,12 @@ import org.springframework.util.Assert;
*
* @author Phillip Webb
* @author Dave Syer
*
* @see TomcatEmbeddedServletContainerFactory
*/
public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer {
private final Log logger = LogFactory.getLog(TomcatEmbeddedServletContainer.class);
private final Tomcat tomcat;
/**
@ -43,10 +47,10 @@ public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer
public TomcatEmbeddedServletContainer(Tomcat tomcat) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
start();
initialize();
}
private synchronized void start() throws EmbeddedServletContainerException {
private synchronized void initialize() throws EmbeddedServletContainerException {
try {
this.tomcat.start();
// Unlike Jetty, all Tomcat threads are daemon threads. We create a
@ -66,6 +70,19 @@ public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer
}
}
@Override
public void start() throws EmbeddedServletContainerException {
Connector connector = this.tomcat.getConnector();
if (connector != null) {
try {
connector.getProtocolHandler().resume();
}
catch (Exception e) {
this.logger.error("Cannot start connector: ", e);
}
}
}
@Override
public synchronized void stop() throws EmbeddedServletContainerException {
try {

View File

@ -26,14 +26,10 @@ import java.util.List;
import org.apache.catalina.Context;
import org.apache.catalina.Host;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Server;
import org.apache.catalina.Valve;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.core.StandardService;
import org.apache.catalina.loader.WebappLoader;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.Tomcat.FixContextListener;
@ -45,7 +41,6 @@ import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerFa
import org.springframework.bootstrap.context.embedded.ErrorPage;
import org.springframework.bootstrap.context.embedded.ServletContextInitializer;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@ -61,7 +56,6 @@ import org.springframework.util.ClassUtils;
*
* @author Phillip Webb
* @author Dave Syer
*
* @see #setPort(int)
* @see #setContextLifecycleListeners(Collection)
* @see TomcatEmbeddedServletContainer
@ -69,6 +63,8 @@ import org.springframework.util.ClassUtils;
public class TomcatEmbeddedServletContainerFactory extends
AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
private static final String DEFAULT_PROTOCOL = "org.apache.coyote.http11.Http11NioProtocol";
private File baseDirectory;
private List<Valve> contextValves = new ArrayList<Valve>();
@ -77,9 +73,7 @@ public class TomcatEmbeddedServletContainerFactory extends
private ResourceLoader resourceLoader;
private Connector connector;
private Tomcat tomcat = new Tomcat();
private String protocol = DEFAULT_PROTOCOL;
/**
* Create a new {@link TomcatEmbeddedServletContainerFactory} instance.
@ -107,41 +101,23 @@ public class TomcatEmbeddedServletContainerFactory extends
super(contextPath, port);
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
Connector connector = this.connector;
if (connector != null) {
try {
connector.getProtocolHandler().resume();
}
catch (Exception e) {
this.logger.error("Cannot start connector: ", e);
}
}
}
@Override
public EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers) {
Connector connector;
Tomcat tomcat = new Tomcat();
if (getPort() == 0) {
return EmbeddedServletContainer.NONE;
}
File baseDir = (this.baseDirectory != null ? this.baseDirectory
: createTempDir("tomcat"));
this.tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = this.connector;
if (connector != null) {
connector.setPort(getPort());
this.tomcat.getService().addConnector(connector);
this.tomcat.setConnector(connector);
}
else {
connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
customizeConnector(connector);
this.tomcat.getService().addConnector(connector);
this.tomcat.setConnector(connector);
}
this.connector = connector;
tomcat.setBaseDir(baseDir.getAbsolutePath());
connector = new Connector(this.protocol);
customizeConnector(connector);
tomcat.getService().addConnector(connector);
tomcat.setConnector(connector);
try {
// Allow the server to start so the ServletContext is available, but stop the
// connector to prevent requests from being handled before the Spring context
@ -151,11 +127,11 @@ public class TomcatEmbeddedServletContainerFactory extends
catch (Exception e) {
this.logger.error("Cannot pause connector: ", e);
}
this.tomcat.getHost().setAutoDeploy(false);
this.tomcat.getEngine().setBackgroundProcessorDelay(-1);
tomcat.getHost().setAutoDeploy(false);
tomcat.getEngine().setBackgroundProcessorDelay(-1);
prepareContext(this.tomcat.getHost(), initializers);
return getTomcatEmbeddedServletContainer(this.tomcat);
prepareContext(tomcat.getHost(), initializers);
return getTomcatEmbeddedServletContainer(tomcat);
}
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
@ -293,12 +269,12 @@ public class TomcatEmbeddedServletContainerFactory extends
}
/**
* The tomcat connector to use (e.g. if you want to change the protocol handler).
*
* @param connector the connector to set
* The Tomcat protocol to use when create the {@link Connector}.
* @see Connector#Connector(String)
*/
public void setConnector(Connector connector) {
this.connector = connector;
public void setProtocol(String protocol) {
Assert.hasLength(protocol, "Protocol must not be empty");
this.protocol = protocol;
}
/**
@ -363,47 +339,4 @@ public class TomcatEmbeddedServletContainerFactory extends
this.contextLifecycleListeners.addAll(Arrays.asList(contextLifecycleListeners));
}
// FIXME JavaDoc
// FIXME Is this still needed?
public TomcatEmbeddedServletContainerFactory getChildContextFactory(final String name) {
final Server server = this.tomcat.getServer();
return new TomcatEmbeddedServletContainerFactory() {
@Override
public EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers) {
if (getPort() == 0) {
return EmbeddedServletContainer.NONE;
}
StandardService service = new StandardService();
service.setName(name);
Connector connector = new Connector(
"org.apache.coyote.http11.Http11NioProtocol");
customizeConnector(connector);
service.addConnector(connector);
StandardEngine engine = new StandardEngine();
engine.setName(name);
engine.setDefaultHost("localhost");
service.setContainer(engine);
server.addService(service);
StandardHost host = new StandardHost();
host.setName("localhost");
engine.addChild(host);
prepareContext(host, initializers);
return new EmbeddedServletContainer() {
@Override
public void stop() throws EmbeddedServletContainerException {
// noop
}
};
}
};
}
}

View File

@ -84,6 +84,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
ConfigurableEmbeddedServletContainerFactory factory = getFactory();
this.container = factory
.getEmbeddedServletContainer(exampleServletRegistration());
this.container.start();
assertThat(getResponse(factory, "http://localhost:8080/hello"),
equalTo("Hello World"));
}
@ -94,6 +95,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
factory.setPort(0);
this.container = factory
.getEmbeddedServletContainer(exampleServletRegistration());
this.container.start();
this.thrown.expect(SocketException.class);
getResponse(factory, "http://localhost:8080/hello");
}
@ -103,7 +105,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
ConfigurableEmbeddedServletContainerFactory factory = getFactory();
this.container = factory
.getEmbeddedServletContainer(exampleServletRegistration());
start(factory);
this.container.start();
this.container.stop();
this.thrown.expect(SocketException.class);
getResponse(null, "http://localhost:8080/hello");
@ -115,7 +117,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
ConfigurableEmbeddedServletContainerFactory factory = getFactory();
this.container = factory
.getEmbeddedServletContainer(exampleServletRegistration());
this.container.start();
MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
HttpClient client = new HttpClient(connectionManager);
GetMethod get1 = new GetMethod("http://localhost:8080/hello");
@ -137,6 +139,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
this.container = factory.getEmbeddedServletContainer(
exampleServletRegistration(), new FilterRegistrationBean(
new ExampleFilter()));
this.container.start();
assertThat(getResponse(factory, "http://localhost:8080/hello"),
equalTo("[Hello World]"));
}
@ -159,6 +162,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
}
}
});
this.container.start();
assertThat(date[0], notNullValue());
}
@ -168,6 +172,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
factory.setPort(8081);
this.container = factory
.getEmbeddedServletContainer(exampleServletRegistration());
this.container.start();
assertThat(getResponse(factory, "http://localhost:8081/hello"),
equalTo("Hello World"));
}
@ -178,6 +183,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
factory.setContextPath("/say");
this.container = factory
.getEmbeddedServletContainer(exampleServletRegistration());
this.container.start();
assertThat(getResponse(factory, "http://localhost:8080/say/hello"),
equalTo("Hello World"));
}
@ -209,7 +215,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
ConfigurableEmbeddedServletContainerFactory factory = getFactory();
this.container = factory
.getEmbeddedServletContainer(exampleServletRegistration());
start(factory);
this.container.start();
this.container.stop();
this.container.stop();
}
@ -225,6 +231,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
factory.addInitializers(initializers[4], initializers[5]);
this.container = factory.getEmbeddedServletContainer(initializers[0],
initializers[1]);
this.container.start();
InOrder ordered = inOrder((Object[]) initializers);
for (ServletContextInitializer initializer : initializers) {
ordered.verify(initializer).onStartup((ServletContext) anyObject());
@ -238,6 +245,7 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
AbstractEmbeddedServletContainerFactory factory = getFactory();
factory.setDocumentRoot(this.temporaryFolder.getRoot());
this.container = factory.getEmbeddedServletContainer();
this.container.start();
assertThat(getResponse(factory, "http://localhost:8080/test.txt"),
equalTo("test"));
}
@ -246,7 +254,6 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
protected String getResponse(EmbeddedServletContainerFactory factory, String url)
throws IOException, URISyntaxException {
start(factory);
SimpleClientHttpRequestFactory clientHttpRequestFactory = new SimpleClientHttpRequestFactory();
ClientHttpRequest request = clientHttpRequestFactory.createRequest(new URI(url),
HttpMethod.GET);
@ -259,12 +266,6 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
}
}
private void start(EmbeddedServletContainerFactory factory) {
if (factory instanceof AbstractEmbeddedServletContainerFactory) {
((AbstractEmbeddedServletContainerFactory) factory).onApplicationEvent(null);
}
}
protected abstract AbstractEmbeddedServletContainerFactory getFactory();
private ServletContextInitializer exampleServletRegistration() {

View File

@ -31,10 +31,6 @@ import javax.servlet.ServletRegistration;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.bootstrap.context.embedded.AbstractEmbeddedServletContainerFactory;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainer;
import org.springframework.bootstrap.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.bootstrap.context.embedded.ServletContextInitializer;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.anyObject;
@ -93,10 +89,10 @@ public class MockEmbeddedServletContainerFactory extends
int port) {
this.initializers = initializers;
this.port = port;
start();
initialize();
}
private void start() {
private void initialize() {
try {
this.servletContext = mock(ServletContext.class);
given(this.servletContext.addServlet(anyString(), (Servlet) anyObject()))
@ -157,6 +153,10 @@ public class MockEmbeddedServletContainerFactory extends
}
}
@Override
public void start() throws EmbeddedServletContainerException {
}
@Override
public void stop() {
this.servletContext = null;