diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainer.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainer.java index 3f9ad91a975..95a3eb98004 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainer.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainer.java @@ -21,6 +21,7 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import org.apache.catalina.Container; +import org.apache.catalina.Context; import org.apache.catalina.Engine; import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleState; @@ -91,6 +92,9 @@ public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer // We can re-throw failure exception directly in the main thread rethrowDeferredStartupExceptions(); + ClassLoader classLoader = findContext().getLoader().getClassLoader(); + Thread.currentThread().setContextClassLoader(classLoader); + // Unlike Jetty, all Tomcat threads are daemon threads. We create a // blocking non-daemon to stop immediate shutdown startDaemonAwaitThread(); @@ -101,6 +105,15 @@ public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer } } + private Context findContext() { + for (Container child : this.tomcat.getHost().findChildren()) { + if (child instanceof Context) { + return (Context) child; + } + } + throw new IllegalStateException("The host does not contain a Context"); + } + private void addInstanceIdToEngineName() { int instanceId = containerCounter.incrementAndGet(); if (instanceId > 0) { @@ -245,6 +258,10 @@ public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer ex); } finally { + if (Thread.currentThread() + .getContextClassLoader() instanceof TomcatEmbeddedWebappClassLoader) { + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + } containerCounter.decrementAndGet(); } } diff --git a/spring-boot/src/test/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactoryTests.java b/spring-boot/src/test/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactoryTests.java index cad1a1be231..b2ddd7e12f0 100644 --- a/spring-boot/src/test/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactoryTests.java +++ b/spring-boot/src/test/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactoryTests.java @@ -38,6 +38,7 @@ import org.apache.catalina.connector.Connector; import org.apache.catalina.startup.Tomcat; import org.apache.catalina.valves.RemoteIpValve; import org.apache.coyote.http11.AbstractHttp11JsseProtocol; +import org.junit.After; import org.junit.Test; import org.mockito.InOrder; @@ -72,6 +73,11 @@ public class TomcatEmbeddedServletContainerFactoryTests return new TomcatEmbeddedServletContainerFactory(0); } + @After + public void restoreTccl() { + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + } + // JMX MBean names clash if you get more than one Engine with the same name... @Test public void tomcatEngineNames() throws Exception { @@ -358,6 +364,17 @@ public class TomcatEmbeddedServletContainerFactoryTests assertThat(s3.split(":")[0]).as(message).isNotEqualTo(s2.split(":")[1]); } + @Test + public void tcclOfMainThreadIsTomcatWebAppClassLoader() { + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + TomcatEmbeddedServletContainerFactory factory = getFactory(); + this.container = factory.getEmbeddedServletContainer(); + this.container.start(); + assertThat(Thread.currentThread().getContextClassLoader()) + .isInstanceOf(TomcatEmbeddedWebappClassLoader.class); + this.container.stop(); + } + @Override protected Wrapper getJspServlet() { Container context = ((TomcatEmbeddedServletContainer) this.container).getTomcat()