From 5f57578b714576f68589fbfc7c8205e2d31e3027 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Tue, 31 Oct 2017 22:46:08 +0100 Subject: [PATCH] Enable APR optional support in Tomcat servers This commits adds the AprLifecycleListener as a default LifecycleListener in order to detect and use automatically the tomcatnative library if it is available, for SSL support. This feature can be useful for both performance reasons or for supporting ALPN when using JDK8. See gh-10043 Closes gh-9964 --- .../TomcatReactiveWebServerFactory.java | 40 +++++++++++++++++++ .../tomcat/TomcatServletWebServerFactory.java | 3 +- .../TomcatReactiveWebServerFactoryTests.java | 28 +++++++++++++ .../TomcatServletWebServerFactoryTests.java | 9 +++++ 4 files changed, 79 insertions(+), 1 deletion(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java index 47f47aeca31..e09544be976 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactory.java @@ -24,7 +24,9 @@ import java.util.List; import org.apache.catalina.Context; import org.apache.catalina.Host; +import org.apache.catalina.LifecycleListener; import org.apache.catalina.connector.Connector; +import org.apache.catalina.core.AprLifecycleListener; import org.apache.catalina.loader.WebappLoader; import org.apache.catalina.startup.Tomcat; import org.apache.coyote.AbstractProtocol; @@ -53,6 +55,8 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac private String protocol = DEFAULT_PROTOCOL; + private List contextLifecycleListeners = Arrays.asList(new AprLifecycleListener()); + private List tomcatContextCustomizers = new ArrayList<>(); private List tomcatConnectorCustomizers = new ArrayList<>(); @@ -115,6 +119,9 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac * @param context the Tomcat context */ protected void configureContext(Context context) { + for (LifecycleListener lifecycleListener : this.contextLifecycleListeners) { + context.addLifecycleListener(lifecycleListener); + } for (TomcatContextCustomizer customizer : this.tomcatContextCustomizers) { customizer.customize(context); } @@ -211,6 +218,39 @@ public class TomcatReactiveWebServerFactory extends AbstractReactiveWebServerFac return this.tomcatConnectorCustomizers; } + /** + * Set {@link LifecycleListener}s that should be applied to the Tomcat {@link Context} + * . Calling this method will replace any existing listeners. + * @param contextLifecycleListeners the listeners to set + */ + public void setContextLifecycleListeners( + Collection contextLifecycleListeners) { + Assert.notNull(contextLifecycleListeners, + "ContextLifecycleListeners must not be null"); + this.contextLifecycleListeners = new ArrayList<>(contextLifecycleListeners); + } + + /** + * Returns a mutable collection of the {@link LifecycleListener}s that will be applied + * to the Tomcat {@link Context} . + * @return the context lifecycle listeners that will be applied + */ + public Collection getContextLifecycleListeners() { + return this.contextLifecycleListeners; + } + + /** + * Add {@link LifecycleListener}s that should be added to the Tomcat {@link Context}. + * @param contextLifecycleListeners the listeners to add + */ + public void addContextLifecycleListeners( + LifecycleListener... contextLifecycleListeners) { + Assert.notNull(contextLifecycleListeners, + "ContextLifecycleListeners must not be null"); + this.contextLifecycleListeners.addAll(Arrays.asList(contextLifecycleListeners)); + } + + /** * Factory method called to create the {@link TomcatWebServer}. Subclasses can * override this method to return a different {@link TomcatWebServer} or apply diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java index 6ea4fa90f7e..3805340e2d7 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactory.java @@ -50,6 +50,7 @@ import org.apache.catalina.WebResourceRoot.ResourceSetType; import org.apache.catalina.WebResourceSet; import org.apache.catalina.Wrapper; import org.apache.catalina.connector.Connector; +import org.apache.catalina.core.AprLifecycleListener; import org.apache.catalina.loader.WebappLoader; import org.apache.catalina.session.StandardManager; import org.apache.catalina.startup.Tomcat; @@ -122,7 +123,7 @@ public class TomcatServletWebServerFactory extends AbstractServletWebServerFacto private List contextValves = new ArrayList<>(); - private List contextLifecycleListeners = new ArrayList<>(); + private List contextLifecycleListeners = Arrays.asList(new AprLifecycleListener()); private List tomcatContextCustomizers = new ArrayList<>(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactoryTests.java index bbff214c0ff..25a33607846 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatReactiveWebServerFactoryTests.java @@ -19,13 +19,17 @@ package org.springframework.boot.web.embedded.tomcat; import java.util.Arrays; import org.apache.catalina.Context; +import org.apache.catalina.LifecycleEvent; +import org.apache.catalina.LifecycleListener; import org.apache.catalina.connector.Connector; +import org.apache.catalina.core.AprLifecycleListener; import org.junit.Test; import org.mockito.InOrder; import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactoryTests; import org.springframework.http.server.reactive.HttpHandler; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; @@ -60,6 +64,30 @@ public class TomcatReactiveWebServerFactoryTests } } + @Test + public void defaultTomcatListeners() throws Exception { + TomcatReactiveWebServerFactory factory = getFactory(); + assertThat(factory.getContextLifecycleListeners()) + .hasSize(1) + .first().isInstanceOf(AprLifecycleListener.class); + } + + @Test + public void tomcatListeners() throws Exception { + TomcatReactiveWebServerFactory factory = getFactory(); + LifecycleListener[] listeners = new LifecycleListener[4]; + for (int i = 0; i < listeners.length; i++) { + listeners[i] = mock(LifecycleListener.class); + } + factory.setContextLifecycleListeners(Arrays.asList(listeners[0], listeners[1])); + factory.addContextLifecycleListeners(listeners[2], listeners[3]); + this.webServer = factory.getWebServer(mock(HttpHandler.class)); + InOrder ordered = inOrder((Object[]) listeners); + for (LifecycleListener listener : listeners) { + ordered.verify(listener).lifecycleEvent(any(LifecycleEvent.class)); + } + } + @Test public void setNullConnectorCustomizersShouldThrowException() { TomcatReactiveWebServerFactory factory = getFactory(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java index 917bbe2cf72..d1fdb475577 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/embedded/tomcat/TomcatServletWebServerFactoryTests.java @@ -37,6 +37,7 @@ import org.apache.catalina.Service; import org.apache.catalina.SessionIdGenerator; import org.apache.catalina.Valve; import org.apache.catalina.connector.Connector; +import org.apache.catalina.core.AprLifecycleListener; import org.apache.catalina.core.StandardWrapper; import org.apache.catalina.startup.Tomcat; import org.apache.catalina.util.CharsetMapper; @@ -102,6 +103,14 @@ public class TomcatServletWebServerFactoryTests tomcatWebServer.stop(); } + @Test + public void defaultTomcatListeners() throws Exception { + TomcatServletWebServerFactory factory = getFactory(); + assertThat(factory.getContextLifecycleListeners()) + .hasSize(1) + .first().isInstanceOf(AprLifecycleListener.class); + } + @Test public void tomcatListeners() throws Exception { TomcatServletWebServerFactory factory = getFactory();