How to 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

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?

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;

Parent Last Classloader to solve Java Class path hell?

I managed to solve this problem. Modified the code of the ParentLastClassLoader to get an array of all the Jarfile paths which are needed by the feature. So when a class is loaded, all the jarfiles needed by the feature will be searched for the .class files. If a class file cannot be found, it will be delegated to the parent.

    private  class ParentLastClassLoader extends ClassLoader {

private String[] jarFiles; //Paths to the jar files
private Hashtable classes = new Hashtable(); //used to cache already defined classes

public ParentLastClassLoader(ClassLoader parent, String[] paths)
{
super(parent);
this.jarFiles = paths;
}

@Override
public Class<?> findClass(String name) throws ClassNotFoundException
{
System.out.println("Trying to find");
throw new ClassNotFoundException();
}

@Override
protected synchronized Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException
{
System.out.println("Trying to load");
try
{
System.out.println("Loading class in Child : " + className);
byte classByte[];
Class result = null;

//checks in cached classes
result = (Class) classes.get(className);
if (result != null) {
return result;
}

for(String jarFile: jarFiles){
try {
JarFile jar = new JarFile(jarFile);
JarEntry entry = jar.getJarEntry(className.replace(".","/") + ".class");
InputStream is = jar.getInputStream(entry);
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
int nextValue = is.read();
while (-1 != nextValue) {
byteStream.write(nextValue);
nextValue = is.read();
}

classByte = byteStream.toByteArray();
result = defineClass(className, classByte, 0, classByte.length, null);
classes.put(className, result);
} catch (Exception e) {
continue;
}
}

result = (Class) classes.get(className);
if (result != null) {
return result;
}
else{
throw new ClassNotFoundException("Not found "+ className);
}
}
catch( ClassNotFoundException e ){

System.out.println("Delegating to parent : " + className);
// didn't find it, try the parent
return super.loadClass(className, resolve);
}
}
}

The ParentLastClassLoader is instantiated as follows.

ClassLoader loader = new ParentLastClassLoader(Thread.currentThread().getContextClassLoader(), paths);

Once the ParentLastClassLoader is instantiated, the MainClassOfTheFeature will be loaded and its MainMethod will be invoked.

Using a custom ClassLoader to override existing implementations of classes

Don't skip super class loaders completely. Try something like this?

 @Override protected Class<?> loadClass(String name, boolean resolve) throws 
ClassNotFoundException {
if(name belongs to java classes) {
return super.loadClass(name, resolve);
}
byte[] b = ..// read bytes from your .class files. or use getResourceAsStream(name)
Class<?> c = defineClass(b, name);
if(resolve) resolveClass(c);
return c;
}

How do I create a ClassLoader that will not search the parent for loading classes

Simply use the URLClassLoader and supply null as the parent.

File myDir = new File("/some/directory/");
ClassLoader loader = null;
try {
URL url = myDir.toURL();
URL[] urls = new URL[]{url};
loader = new URLClassLoader(urls, null);
}
catch (MalformedURLException e)
{
// oops
}

ClassLoader: pretend that a class wasn't found so a child loader can handle it instead

The default behavior for loadClass is to call getParent().loadClass and then findClass. To get the behavior you're describing, typically you would not modify the parent class loader; instead, typically you would modify the child class loader by overriding the loadClass method to call findClass first. That way, loadClass calls that originate with the child class loader will load the class from its local class path before the parent, but loadClass calls that originate with the parent class loader will work as expected.

How does the delegation model of class loaders ensure that unique classes are loaded?

how can we end up loading duplicate classes, specially when we check whether or not a class has been loaded before delegating the task to parent loader? https://www.baeldung.com/java-classloaders#classloader-work

The step where the class loader checks whether or not a class has been loaded, i.e. method java.lang.ClassLoader.findLoadedClass, does not imply uniqueness. Imagine two class loaders, CL1 and CL2, both loading a class X. In that case, if CL1 loaded X before CL2, then the check done by CL2 will not return the same class loaded by CL1, because these class loaders do not share this information between them.

The delegation model is what "shares" this information via the parent class loader.



Related Topics



Leave a reply



Submit