I am a bit confused with how the class loading works in the below scenario. Now here's what I know about class loading.
As per the Tomcat docs. The class loading happens in the following order
And if it is not a web-app, then the class loading happens in this order
Bootstrap Loader
System Loader
Application Loader
Now, in my case, I have a web application that reads some serialized data by using an external jar. When trying to read the serialized data, the jar throws ClassNotFoundException
. But if I use my own serialization logic without using the jar, then the application works.
This is the erroneous example
public GenericResult getGenericResult( ){
GenericResult cachedResult = externalJar.get( "myKey" ); // This jar uses deserialization
return cachedResult;
}
This is the working example
public GenericResult getGenericResult( ){
// do not mind resource closing and all as this is a just to show how I did it
FileInputStream fileIn = new FileInputStream(filepath);
ObjectInputStream objectIn = new ObjectInputStream(fileIn);
GenericResult cachedResult = (GenericResult)objectIn.readObject();
return cachedResult;
}
The external jar is a provided jar residing in the tomcats lib directory. What I want to clarify is that, when loading a class from this external jar, is it using the web-application based class loading as I indicated first or java class loading as I indicate second. And what could be the reason for getting a ClassNotFoundException
when trying to load from external jar. Shouldn't the GenericResult
class be found by the class loader at WEB-INF/Classes
??
Exception:
java.lang.ClassNotFoundException: com.example.result.GenericResult
at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:435) ~[?:?]
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:589) ~[?:?]
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522) ~[?:?]
at java.base/java.lang.Class.forName0(Native Method) ~[?:?]
at java.base/java.lang.Class.forName(Class.java:468) ~[?:?]
at java.base/java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:782) ~[?:?]
at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2028) ~[?:?]
at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1895) ~[?:?]
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2202) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1712) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:519) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:477) ~[?:?]
at java.base/java.util.ArrayList.readObject(ArrayList.java:899) ~[?:?]
at java.base/jdk.internal.reflect.GeneratedMethodAccessor1272.invoke(Unknown Source) ~[?:?]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[?:?]
at java.base/java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1226) ~[?:?]
at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2401) ~[?:?]
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2235) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1712) ~[?:?]
at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2540) ~[?:?]
at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2434) ~[?:?]
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2235) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1712) ~[?:?]
at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2540) ~[?:?]
at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2434) ~[?:?]
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2235) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1712) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:519) ~[?:?]
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:477) ~[?:?]
ObjectInputStream#readObject
calls ObjectInputStream#resolveClass
to retrieve objects. The default implementation uses the first loader on the current thread's stack, therefore it uses the common loader in your case. A classloader can find only his own classes and those of its ancestors, so it can not find the classes in your web application.
If you want to deserialize an object in your web application, you need to use the web application's class loader, which is set as context class loader on the current thread. Therefore you need to extend ObjectInputStream
like this:
public class ClassloaderAwareObjectInputStream extends ObjectInputStream {
public ClassloaderAwareObjectInputStream(InputStream in) throws IOException {
super(in);
}
@Override
protected Class< ? > resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
final ClassLoader tccl = Thread.currentThread().getContextClassLoader();
final String name = desc.getName();
try {
return Class.forName(name, false, tccl);
} catch (ClassNotFoundException ex) {
return super.resolveClass(desc);
}
}
}
Edit: The Common classloader can not load a class from your application, since it uses the usual algorithm:
On the other hand a web application classloader uses the algorithm: