The Problem Domain
By its nature, the JBoss Bootstrap project is a simplified entry point into a system that may deploy a series of complex services. The bootstrap facade enables loose coupling between the server client and the individual services themselves; the client need not have any references to or knowledge of the server deployments. As such, the compilation classpath may keep a slim profile.
The same is true of the runtime application classpath. To start the Bootstrap Server, a very minimal set of classes (from the spi-* projects) is required to be visible by the Application ClassLoader (which is responsible for loading classes upon the classpath (System Property "java.class.path"). Once the server is started, additional classes may be demanded by deployments triggered by the Bootstrap. In order to avoid ClassNotFoundException, these must be visible to the current ClassLoader, either in the ClassLoader itself or via its parent.
Solution 1: Include All Runtime Dependencies Upon the Application ClassPath
This is the easiest solution, though it may not be the most desirable/practical. The values for the "--classpath" switch or JAR Manifest equivalent must be known at build time, and the additional dependencies may bloat the distriibution size of your project to an intolerable extent.
By including all runtime dependencies in view of the Application ClassLoader, child ClassLoaders will (typically) delegate up the hierarchy until they've received the requested class. This may be problematic in cases where parent delegation is not supported, for instance in scoped/isolated ClassLoading as defined by the Java Servlet Specification.
Solution 2: Create a ClassLoader to Load Runtime Dependencies
A more dynamic solution is to place a minimal set of classes into the application classpath, then load what's needed for server start into a separate ClassLoader. This allows us to start the JVM using a very minimal dependency set.
In practice this may look something like this:
// Construct a CL to see our runtime dependencies final ClassLoader tccl = Thread.currentThread().getContextClassLoader(); final URL[] urls = null; // Assume we have a bunch of URLs containing JARs of classes we need final ClassLoader runtimeDepsCl = new URLClassLoader(urls,tccl); // Create the server final Server<?,?> server = ServerFactory.createServer("fully.qualified.ServerName",runtimeDepsCl); // Set the current CL to ours Thread.currentThread().setContextClassLoader(runtimeDepsCl); try { server.start(); // Do stuff, then... server.shutdown(); } finally { // Restore the TCCL Thread.currentThread().setContextClassLoader(tccl); // Close (JDK7 only) if(runtimeDepsCl instanceof Closeable) { try { runtimeDepsCl.close(); } catch (final IOException ioe) {// Ignore} } }
A common problem encountered here is the "NoClassDefFoundError", which indicates an error in linkage. Classes are equal through a composite of their fully-qualified class name and defining ClassLoader. Consider the following case:
- The Application ClassPath contains ClassX
- ClassX has a reference to ClassY, which is not on the Application ClassPath
- We create a URLClassLoader to load in a bunch of extra libraries
- ClassY is included in one of the JARs in the URLCL
- A deployment referenced by the Server's Bootstrap requests ClassX.
- NoClassDefFoundError: Cannot load ClassX
Even though both ClassX and ClassY are visible from the URLCL, ClassX is loaded by the Application ClassLoader. The Application ClassLoader cannot see ClassY because there is no notion of parent > child delegation in Java. Therefore the linkage error is thrown.
This may occur commonly if the JBoss Microcontainer classes are loaded via a non-Application ClassLoader. jboss-bootstrap-spi-mc contains references to the MC Kernel, so if the Kernel itself (and all of its dependencies in turn) are not visible to the CL which loaded the MCServer, NCDFE is raised.
When using the dynamic ClassLoading approach we must take proper care not to leak class references in unintended ClassLoading scopes.
Comments