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 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();
}
}

@ -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()

Loading…
Cancel
Save