Classloader security exists to protect application servers and frameworks from corruption by untested and unapproved versions of classes. In web servers, for example, the core webserver engine needs to be protected from affect by the twenty various applications from different customers otherwise one malicious WAR could infiltrate all the others by surgically replacing a core class with another. It protects the ecosystem using layers of classloaders. So, to help resolve the next ClassNotFoundException it pays to understand how layered classloading works.
Let's use Tomcat, the application server as a simple example.
When Tomcat is executed, it has a default classloader (the one that the Java Virtual Machine starts for it). It can load anything from the classpath using this classloader, including any classes included in the ${TOMCAT_HOME}/lib folder.
However, Tomcat then loads a forum.war file which wants to register some new JAR files of code that relates only to execution of a Forum website. It does this by creating a “delegating” classloader, which keeps a reference to the System classloader so it can first try to serve up a resource from forum.war then fall back to the System classloader and so on up the tree.
Classloader Layer |
Loads |
---|---|
Bootstrap/root Classloader |
Core java classes for the JVM, from JAR files in the ${JAVA_HOME}/jre/lib folder |
Extensions Classloader |
Extensions java classes for the JVM, from JAR files in the ${JAVA_HOME}/jre/lib/ext folder |
System/Application Classloader |
Classes specific to the current application, which is Tomcat. This loads classes from JAR files in ${TOMCAT_HOME}/lib. |
Delegate Classloader |
A new classloader Tomcat provides that can load any JAR defined in a WAR file |
The general rule is: If code requires another class to execute, it will first ask the classloader that instantiated it, or deeper to find the new class definition. So if a class was previously provided by the Delegate Classloader and it referred to a new class, the JVM would first query the Delegate Classloader, then System, Extensions and Bootstrap classloaders until it found a definition.
So a class in a delegate classloader can access classes in the root classloader but not the other way around. This part is important when debugging classloader problems: classes in the root classloader cannot normally load classes from delegate classloaders – for good reason; what if there were twenty concurrent build scripts running at a time, which delegate classloader would it load the classes from?
The Delegating classloader is like a parent/child relationship.
A child classloader remembers exactly who it's parent is, and can ask them if they know how to resolve class names. The parent classloader does not remember which children were created from it, so can't ask them for classes.
An example of the Delegating Classloader in the real world would be a family situation:
A child Jenny already has fork but wants a spoon. Based on where she found the fork, she can look to see if there is a spoon there. If the spoon is not there, Jenny can ask her parent Thomas (whom she remembers very clearly) if he can provide her a spoon.
Their parent Thomas has a fork but wants a spoon. Based on where he found the fork, he can look to see if there is a spoon there. If the spoon is not there, he doesn't keep track of his children or has too many so he doesn't know who to start asking for a spoon. He could ask his parent though, as remembers who that is. And so on.
Trap: In appservers like Websphere you could choose if you wanted to load classes from bottom-up or top-down, specifically for each ear you had deployed.
Other appservers are different, so you can't depend on this and should not use such a feature.
Given the complexity, think twice before doing anything special with classloaders. It might become tech debt the moment you leave the project.