Set main thread's context class loader when starting Tomcat

When an app is deployed to Tomcat, all of the application's startup
is performed with a WebAppClassLoader being the thread context
class loader. When an app is using embedded Tomcat, the
WebAppClassLoader is created as part of the application starting but
is never set as the thread context class loader. This difference
in TCCL can cause problems. For example, it breaks the use of JNDI
during application startup with embedded Tomcat.

This commit updates the embedded Tomcat servlet container to set
the TCCL to be the WebAppClassLoader once the Tomcat context has
been started. Once Tomcat is stopped, it sets the TCCL back to the
ClassLoader that loaded it.

Closes gh-2308
pull/5052/merge
Andy Wilkinson 9 years ago
parent d6e0b5a165
commit ff99bb0730

@ -21,6 +21,7 @@ import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import org.apache.catalina.Container; import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Engine; import org.apache.catalina.Engine;
import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleState; 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 // We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions(); rethrowDeferredStartupExceptions();
ClassLoader classLoader = findContext().getLoader().getClassLoader();
Thread.currentThread().setContextClassLoader(classLoader);
// Unlike Jetty, all Tomcat threads are daemon threads. We create a // Unlike Jetty, all Tomcat threads are daemon threads. We create a
// blocking non-daemon to stop immediate shutdown // blocking non-daemon to stop immediate shutdown
startDaemonAwaitThread(); 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() { private void addInstanceIdToEngineName() {
int instanceId = containerCounter.incrementAndGet(); int instanceId = containerCounter.incrementAndGet();
if (instanceId > 0) { if (instanceId > 0) {
@ -245,6 +258,10 @@ public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer
ex); ex);
} }
finally { finally {
if (Thread.currentThread()
.getContextClassLoader() instanceof TomcatEmbeddedWebappClassLoader) {
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
}
containerCounter.decrementAndGet(); containerCounter.decrementAndGet();
} }
} }

@ -38,6 +38,7 @@ import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat; import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.valves.RemoteIpValve; import org.apache.catalina.valves.RemoteIpValve;
import org.apache.coyote.http11.AbstractHttp11JsseProtocol; import org.apache.coyote.http11.AbstractHttp11JsseProtocol;
import org.junit.After;
import org.junit.Test; import org.junit.Test;
import org.mockito.InOrder; import org.mockito.InOrder;
@ -72,6 +73,11 @@ public class TomcatEmbeddedServletContainerFactoryTests
return new TomcatEmbeddedServletContainerFactory(0); 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... // JMX MBean names clash if you get more than one Engine with the same name...
@Test @Test
public void tomcatEngineNames() throws Exception { public void tomcatEngineNames() throws Exception {
@ -358,6 +364,17 @@ public class TomcatEmbeddedServletContainerFactoryTests
assertThat(s3.split(":")[0]).as(message).isNotEqualTo(s2.split(":")[1]); 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 @Override
protected Wrapper getJspServlet() { protected Wrapper getJspServlet() {
Container context = ((TomcatEmbeddedServletContainer) this.container).getTomcat() Container context = ((TomcatEmbeddedServletContainer) this.container).getTomcat()

Loading…
Cancel
Save