Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I create a parent-last / child-first ClassLoader in Java, or How to override an old Xerces version that was already loaded in the parent CL?

I would like to create a parent-last / child-first class loader, e.g. a class loader that will look for classes in the child class loder first, and only then delegate to it's parent ClassLoader to search for classes.

Clarification:

I know now that to get complete ClassLoading seperation I need to use something like a URLClassLoader passing null as it's parent, thanks to this answer to my previous question

However the current question comes to help me resolve this issue:

  1. My code + dependent jars are being loaded into an existing system, using a ClassLoader that sets that System's ClassLoader as it's parent (URLClassLoader)

  2. That System uses some libraries of a version not compatible with the one I need (e.g. older version of Xerces, that doesn't allow me to run my code)

  3. My code runs perfectly fine if runs stand alone, but it fails if runs from that ClassLoader

  4. Howerver I do need access to many other classes within the parent ClassLoader

  5. Therefore I want to allow me to Override, the parent classloader "jars" with my own: If a class I call is found in the child class loader (e.g. I provided a newer version of Xerces with my own jars, instead of the one users by the ClassLoader that loaded my code and jars.

Here is the System's code that loads my code + Jars (I can't change this one)

File addOnFolder = new File("/addOns");  URL url = addOnFolder.toURL();          URL[] urls = new URL[]{url}; ClassLoader parent = getClass().getClassLoader(); cl = URLClassLoader.newInstance(urls, parent); 

Here is "my" code (taken fully from the Flying Sauser "Hello World" code demo):

package flyingsaucerpdf;  import java.io.*; import com.lowagie.text.DocumentException; import org.xhtmlrenderer.pdf.ITextRenderer;  public class FirstDoc {      public static void main(String[] args)              throws IOException, DocumentException {          String f = new File("sample.xhtml").getAbsolutePath();         System.out.println(f);         //if(true) return;         String inputFile = "sample.html";         String url = new File(inputFile).toURI().toURL().toString();         String outputFile = "firstdoc.pdf";         OutputStream os = new FileOutputStream(outputFile);          ITextRenderer renderer = new ITextRenderer();         renderer.setDocument(url);         renderer.layout();         renderer.createPDF(os);          os.close();     } } 

This works standalone (running main) but fails with this error when loaded through the parent CL:

org.w3c.dom.DOMException: NAMESPACE_ERR: An attempt is made to create or change an object in a way which is incorrect with regard to namespaces.

probably because the parent system uses Xerces of an older version, and even though I provide the right Xerces jar in the /addOns folder, since it's classes were already loaded and used by the parent System, it doesn't allow my own code to use my own jar due to the direction of the delegation. I hope this makes my question clearer, and I'm sure it has been asked before. (Perhaps I don't ask the right question)

like image 785
Eran Medan Avatar asked Mar 26 '11 21:03

Eran Medan


People also ask

How do I get parent ClassLoader?

getParent() method returns the parent class loader for delegation. Some implementations may use null to represent the bootstrap class loader. This method will return null in such implementations if this class loader's parent is the bootstrap class loader.

What is parent ClassLoader?

The parent class loader, in turn, goes through the same process of asking its parent. This chain of delegation continues through to the bootstrap class loader (also known as the primordial or system class loader). If a class loader's parent can load a given class, it returns that class.

Can we load the same class by two ClassLoader?

So when a class is loaded into JVM, you have an entry as (package, classname, classloader). Therefore the same class can be loaded twice by two different ClassLoader instances.

What is the arrangement of ClassLoader in Java environment?

Extension ClassLoader: The Extension ClassLoader is a child of Bootstrap ClassLoader and loads the extensions of core java classes from the respective JDK Extension library. It loads files from jre/lib/ext directory or any other directory pointed by the system property java. ext. dirs.


1 Answers

Today is your lucky day, as I had to solve this exact problem. I warn you though, the innards of class loading are a scary place. Doing this makes me think that the designers of Java never imagined that you might want to have a parent-last classloader.

To use just supply a list of URLs containing classes or jars to be available in the child classloader.

/**  * A parent-last classloader that will try the child classloader first and then the parent.  * This takes a fair bit of doing because java really prefers parent-first.  *   * For those not familiar with class loading trickery, be wary  */ private static class ParentLastURLClassLoader extends ClassLoader  {     private ChildURLClassLoader childClassLoader;      /**      * This class allows me to call findClass on a classloader      */     private static class FindClassClassLoader extends ClassLoader     {         public FindClassClassLoader(ClassLoader parent)         {             super(parent);         }          @Override         public Class<?> findClass(String name) throws ClassNotFoundException         {             return super.findClass(name);         }     }      /**      * This class delegates (child then parent) for the findClass method for a URLClassLoader.      * We need this because findClass is protected in URLClassLoader      */     private static class ChildURLClassLoader extends URLClassLoader     {         private FindClassClassLoader realParent;          public ChildURLClassLoader( URL[] urls, FindClassClassLoader realParent )         {             super(urls, null);              this.realParent = realParent;         }          @Override         public Class<?> findClass(String name) throws ClassNotFoundException         {             try             {                 // first try to use the URLClassLoader findClass                 return super.findClass(name);             }             catch( ClassNotFoundException e )             {                 // if that fails, we ask our real parent classloader to load the class (we give up)                 return realParent.loadClass(name);             }         }     }      public ParentLastURLClassLoader(List<URL> classpath)     {         super(Thread.currentThread().getContextClassLoader());          URL[] urls = classpath.toArray(new URL[classpath.size()]);          childClassLoader = new ChildURLClassLoader( urls, new FindClassClassLoader(this.getParent()) );     }      @Override     protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException     {         try         {             // first we try to find a class inside the child classloader             return childClassLoader.findClass(name);         }         catch( ClassNotFoundException e )         {             // didn't find it, try the parent             return super.loadClass(name, resolve);         }     } } 

EDIT: Sergio and ɹoƃı have pointed out that if you call .loadClass with the same classname, you will get a LinkageError. While this is true, the normal use-case for this classloader is to set it as the thread's classloader Thread.currentThread().setContextClassLoader() or via Class.forName(), and that works as-is.

However, if .loadClass() was needed directly, this code could be added in the ChildURLClassLoader findClass method at the top.

                Class<?> loaded = super.findLoadedClass(name);                 if( loaded != null )                     return loaded; 
like image 113
karoberts Avatar answered Oct 18 '22 02:10

karoberts