12
votes

I am completely confused over how the class loading happens in tomcat. So please bear with me if my question sounds stupid.

We deploy multiple spring webapps on single tomcat server. To reduce the memory footprint, we thought of having spring, hibernate and database driver jars in tomcat lib folder so that those are shared by all webapps.

I started first by marking spring dependencies as provided and copied those jars to tomcat lib. But on server startup I started getting multiple ClassNotFoundErrors, like for commons-logging, commons-fileupload, jackson, so I had to move these jars as well to tomcat lib.

But then this started feeling fishy. What if in future I add another spring dependency to my project, say spring-data-cassandra. Would I need to move it's dependent jars as well? This might be unending. Also I might get CNF errors at runtime.

I tried to follow this link and brought back spring-context and spring-web back to application war. But it didn't work, got ClassNotFound on some class during WebApplicationIntializer initialization. I tried to understand the order of classes getting loaded in tomcat, but could not understand much.

Then I found a complete different explanation for JDBC driver loading which kinda contradicts to all other explanations and left me completely confused.



As I read more, I think it is not a right approach to move spring jars to tomcat lib, but still haven't got a good reasoning. And then why JDBC driver works? Can someone please explain? Also does classloader for each webapp creates a copy of each class?

Edit 1: I came to know that few dependencies are optional in spring jars, and will be required if are in use in my webapp. So spring-web depends on jackson libraries but is optional for my app. So I need to find out which all jars are required for my project and are also required by spring, those jars need to be moved to tomcat lib.

2

2 Answers

4
votes

I will try to explain what I know.

When you deploy a WAR on your tomcat, the class loading will happen this way :

  1. look for the class to load in your WAR classLoader
  2. if not found move to parent (tomcat /lib folder)

What happen in your case is that spring also have a lot of dependencies, if you package it in your war, its dependencies would have been packaged as well and everything would have worked fine. But since you defined spring as provided, all its dependencies are considered provided as well, and when you put it in /lib folder, spring is accessible, but its dependencies are not.

What you need to do is put all spring dependencies and the dependencies of dependencies (etc.) in lib folder as well. Another solution is to define an intermediary WAR in your classloading hierarchy which will contains all your common libs.

0
votes

If you prefer to create this type of thin WAR, one solution would be to first gather all runtime/provided dependencies and then copy them explicitly under Tomcat's common (or shared) library directory:

If under a Maven project, gather everything (including transitive dependencies) under target/dependency:

mvn dependency:copy-dependencies -DincludeScope=runtime
mvn dependency:copy-dependencies -DincludeScope=provided

Afterwards, copy to library. For example:

cp target/dependency/*.jar /usr/local/tomcat/lib/