Search code examples
javajsontomcatclassloader

Tomcat custom classloader failed to load some jars


I have a web application that have an upload functionnality which consist of uploading a package containing a java application (it may contains multiple dependencies)

For that , for every uploaded application, i'm creating a custom classloader to dynamically load the application classpath. The solution is working fine until I get this error when uploading a new application:

javax.xml.stream.FactoryConfigurationError: Error creating stream factory: java.lang.ClassNotFoundException: de.odysseus.staxon.json.stream.impl.JsonStreamFactoryImpl

I have verified that my uploaded package contains staxon and also verifyed that my custom classloader can load that class:

Object a=Class.forName("de.odysseus.staxon.json.stream.impl.JsonStreamFactoryImpl",true , classLoader).newInstance();

So, why this exception and specially for this jar?


Solution

  • When you call Class.forName(className, initialize, classLoader) you are asking the JVM to load a class using that custom class loader. If you don't specify a ClassLoader -- for example just by calling Class.forName(className), then the JVM will use the ClassLoader known as the "context ClassLoader" for the thread. Each thread has one of these, and your code can change the context classloader somewhat easily.

    If you have some code that causes a class to be loaded -- something like this:

    MyClass foo = new MyClass();
    

    The MyClass class will be loaded by the ClassLoader if it's not already loaded. It's not possible to call a constructor and supply a ClassLoader to load the class in case it's not already loaded. In this case, the thread's context ClassLoader is user.

    Furthermore, if you call some code that you don't control, there are many ways in which that code could cause other classes to be loaded, and since you have no control over that code, the thread's context ClassLoader will also be used.

    So, how do you set the thread's context ClassLoader? Easy:

    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    ClassLoader myClassLoader = ...; // You figure this out
    try
    {
        Thread.currentThread().setContextClassLoader(myClassLoader);
    
        // Do work that requires your ClassLoader to work
    }
    finally
    {
        // Always restore the previous CCL after work is done
        Thread.currentThread().setContextClassLoader(cl);
    }
    

    Another thing you'll want to do it make sure that your custom ClassLoader delegates any requests for class-loading to a parent ClassLoader. That parent ClassLoader should probably be the ClassLoader that would naturally be used if you weren't trying to use your own: the thread's context ClassLoader.

    So, you probably want something like the following:

    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    ClassLoader myClassLoader = new MyClassLoader(cl); // Try 'cl' before your custom class loading
    
    try
    {
        Thread.currentThread().setContextClassLoader(myClassLoader);
    
        // Do work that requires your ClassLoader to work
    }
    finally
    {
        // Always restore the previous CCL after work is done
        Thread.currentThread().setContextClassLoader(cl);
    }
    

    You can find more information about ClassLoaders, and, specifically the TCCL, in these few references: