Can I create a custom classpath on a per application basis in Tomcat

34,077

Solution 1

From Tomcat 7 there is no mention of not being able to use the VirtualWebappLoader in production. I tried it and it works like a dream. Simply add the following to META-INF/context.xml:

<?xml version="1.0" encoding="UTF-8"?>

<Context antiJARLocking="true" path="/websandbox">
    <Loader className="org.apache.catalina.loader.VirtualWebappLoader"
          virtualClasspath="/usr/.../*.jar;/usr/.../*.jar"/>
</Context>

In Netbeans, under packaging, I just untick all the packages, taking the .war size down to nothing, make sure the dependencies are in the correct folders on the server and upload. Yey! No more 100 MB WAR file.

Solution 2

Addition @Spider answer.

Tomcat Context hold Loader element. According to docs deployment descriptor (what in <Context> tag) can be placed in:

  • $CATALINA_BASE/conf/server.xml - bad - require server restarts in order to reread config
  • $CATALINA_BASE/conf/context.xml - bad - shared across all applications
  • $CATALINA_BASE/work/$APP.war:/META-INF/context.xml - bad - require repackaging in order to change config
  • $CATALINA_BASE/work/[enginename]/[hostname]/$APP/META-INF/context.xml - nice, but see last option!!
  • $CATALINA_BASE/webapps/$APP/META-INF/context.xml - nice, but see last option!!
  • $CATALINA_BASE/conf/[enginename]/[hostname]/$APP.xml - best - completely out of application and automatically scanned for changes!!!

Here my config which demonstrate how to use development version of project files out of $CATALINA_BASE hierarchy (note that I place this file into src/test/resources dir and intruct Maven to preprocess ${basedir} placeholders through pom.xml <filtering>true</filtering> so after build in new environment I copy it to $CATALINA_BASE/conf/Catalina/localhost/$APP.xml):

<Context docBase="${basedir}/src/main/webapp"
         reloadable="true">
    <!-- http://tomcat.apache.org/tomcat-7.0-doc/config/context.html -->
    <Resources className="org.apache.naming.resources.VirtualDirContext"
               extraResourcePaths="/WEB-INF/classes=${basedir}/target/classes,/WEB-INF/lib=${basedir}/target/${project.build.finalName}/WEB-INF/lib"/>
    <Loader className="org.apache.catalina.loader.VirtualWebappLoader"
            virtualClasspath="${basedir}/target/classes;${basedir}/target/${project.build.finalName}/WEB-INF/lib"/>
    <JarScanner scanAllDirectories="true"/>

    <!-- Use development version of JS/CSS files. -->
    <Parameter name="min" value="dev"/>
    <Environment name="app.devel.ldap" value="USER" type="java.lang.String" override="true"/>
    <Environment name="app.devel.permitAll" value="true" type="java.lang.String" override="true"/>
</Context>

UPDATE Tomcat 8 change syntax for <Resources> and <Loader> elements, corresponding part now look like:

<Resources>
    <PostResources className="org.apache.catalina.webresources.DirResourceSet"
                   webAppMount="/WEB-INF/classes" base="${basedir}/target/classes" />
    <PostResources className="org.apache.catalina.webresources.DirResourceSet"
                   webAppMount="/WEB-INF/lib" base="${basedir}/target/${project.build.finalName}/WEB-INF/lib" />
</Resources>

Solution 3

Another a bit hacky alternative.

You can write a 5-6 line custom class loader which derives from urlclassloader, and simply adds your classpath jars using addUrl() method.

Then set it as the context class loader of the thread in your application code.

Thread.setContextClassLoader(new CustomClassloader(path, parentClassLoader)

where parent class loader typically is

Thread.getContextClassloader()

Solution 4

This is what the META-INF/context.xml file can be used for. You defined your own WebappLoader, which loads classes for your particular webapp. This is the reference I used: http://tomcat.apache.org/tomcat-5.5-doc/config/loader.html (Edit: for Tomcat 6: http://tomcat.apache.org/tomcat-6.0-doc/config/loader.html, for Tomcat 7: http://tomcat.apache.org/tomcat-7.0-doc/config/loader.html)

Also this fellow here seems to post a solution to your exact problem (example included): http://java.dzone.com/articles/extending-tomcat-webapploader

Share:
34,077
sparkyspider
Author by

sparkyspider

I have a passion for writing complex code in such a simple and understandable (followable) manner that a kid can understand and maintain it.

Updated on October 23, 2020

Comments

  • sparkyspider
    sparkyspider over 3 years

    For some applications I use ZK, others Hibernate, other Apache Commons, etc.

    I don't want to deploy a 75MB war file, just because it uses lots of libraries.

    I don't want to add the libraries to my tomcat lib folder, or nor the classpath to it's configuration as I may have an old application using library x.1 and another application using library x.2

    For this reason, it would be great to have something in the web.xml or context.xml where I say something like:

    <classpath>/usr/local/tomcat/custom-libs/zk-5.0.4</classpath>
    

    Note: The above is pseudo-code

  • acheron55
    acheron55 over 10 years
    Since I was on unix, I was trying to be smart changing semi colons to colons and was wondering what the error was spending an hour
  • gavenkoa
    gavenkoa over 9 years
    No need for to make own WebAppLoader as Tomcat 7 come with VirtualWebappLoader which provide desired feature through virtualClasspath property.
  • Chad
    Chad over 8 years
    btw, your best solution the $APP.xml file will be deleted on subsequent deployment of the war file.
  • gavenkoa
    gavenkoa over 8 years
    @Chad That depends on how you deploy package. When I accidentelly undeploy package through tomcat web manager - it remove that .xml file. From that time I store file backups and even add file to VCS history for each project in test/contrib leaf.
  • Chad
    Chad over 8 years
    Got a nicer solution gavenkoa will post it here later on in the day.
  • RobertG
    RobertG over 8 years
    @Chad: Did you find a better solution?
  • phant0m
    phant0m about 7 years
    @Chad do you still remember what you did?
  • Chad
    Chad about 7 years
    @RobertG see link above.
  • gavenkoa
    gavenkoa about 7 years
    @Chad You way to externalize configs are over-engineered and require additional training outside of official Tomcat documentation but very smart )) Our team uses one Tomcat per app and put configuration in conf/catalina.properties ))
  • Chad
    Chad about 7 years
    @gavenkoa Nice to hear how other developers utilise tomcat. We host about on average 20 to 30 apps per tomcat. It does take 30 minutes to start it up when it crashes although no one has a problem with it. Hopefully in the future we can move to a similar approach, but will keep your technique in mind.
  • Volksman
    Volksman over 6 years
    Awesome! This doesn't just reduce war size (and bandwidth required during deployment time etc) but if you are clever and share library jars of the same version across different app contexts then you can massively reduce the total amount of memory used by tomcat - presuming tomcat reuses existing classes already loaded in memory for the same jar versions. That, in turn, has massive performance benefits due to better use of CPUs L1, L2 cache.
  • Paul Zepernick
    Paul Zepernick over 6 years
    @Chad Thanks for sharing! I have forked your project and modified for Tomcat 9 github.com/zepernick/tomcat-classloader
  • Chad
    Chad over 6 years
    @PaulZepernick Thanks that's great to hear!