0

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?

Zizou
  • 831
  • 1
  • 10
  • 19
  • What is `classLoader`? Is that the custom `ClassLoader` you wrote? What resources did you add to that `ClassLoader` so it can actually load classes? Where is the JSON library itself, and what `ClassLoader` do you expect to load it? – Christopher Schultz Aug 23 '16 at 20:57
  • I'm using URLClassLoader and i'm adding jars containes in my uploaded package to that classloader.The json library is part of these jars (my uploaded package contains mainly a list of jars) – Zizou Aug 24 '16 at 07:55
  • I have tryed to debug my custom classloader to see classes it is trying to load: `public class CustomClassLoader extends URLClassLoader { public CustomClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent); } @Override protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException { System.out.println("trying to load:"+name); return super.loadClass(name, resolve); } } ` But I found that my classloader isn't trying even to load **de.odysseus.staxon.json.stream.impl.JsonStreamFactoryImpl** – Zizou Aug 24 '16 at 08:42
  • You may need to set the thread's context class loader, because lots of events can cause classes to be loaded -- you don't have control over all those situations. – Christopher Schultz Aug 24 '16 at 16:57
  • Thanks Christopher but what do you mean by that?Could you give me an example? – Zizou Aug 24 '16 at 17:12

1 Answers1

0

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:

Community
  • 1
  • 1
Christopher Schultz
  • 20,221
  • 9
  • 60
  • 77
  • Thanks agin Christopher, for loading , I'm using `Class.forName(className, true, myUrlClassLoader)` And another important remark : the problem disappear when i added staxon jar to my web app lib. But I don't like this way because I want to be able to upload new application at the runtime without restarting my server – Zizou Aug 24 '16 at 19:05
  • If the problem goes away when you add the library to your own application's `lib` directory, then the problem is definitely the thread's context classloader. Set it as above and it should work. Remember to make sure that your custom `ClassLoader` delegates to another `ClassLoader` first. If you don't do this, lots of things will break if you change the TCCL to your own. – Christopher Schultz Aug 24 '16 at 19:20
  • There is still one part confusing for me: When you see Christopher **there are many ways in which that code could cause other classes to be loaded, and since you have no control over that code** But when i load a class **cl1** using **Class.forname(myClass,true,myCl)**, I'm expecting that all the classes used by **cl1** will be loaded using the same classloader **myCl**. Isn't this correct? – Zizou Aug 24 '16 at 21:38
  • The JVM only loads classes that are required. It's possible that loading a single class will not load all classes referenced by that class's code. So you can have some of those classes loaded at "runtime" (that is, outside of the `Class.forName` call). – Christopher Schultz Aug 24 '16 at 21:51