36

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

sparkyspider
  • 13,195
  • 10
  • 89
  • 133

4 Answers4

36

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.

sparkyspider
  • 13,195
  • 10
  • 89
  • 133
  • 5
    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 – acheron55 Nov 19 '13 at 17:49
25

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>
gavenkoa
  • 45,285
  • 19
  • 251
  • 303
  • btw, your best solution the $APP.xml file will be deleted on subsequent deployment of the war file. – Chad Sep 02 '15 at 14:42
  • @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. – gavenkoa Sep 03 '15 at 17:52
  • Got a nicer solution gavenkoa will post it here later on in the day. – Chad Sep 04 '15 at 02:01
  • 2
    @Chad: Did you find a better solution? – RobertG Nov 12 '15 at 14:02
  • @Chad do you still remember what you did? – phant0m Feb 22 '17 at 15:44
  • @RobertG see link above. – Chad Feb 24 '17 at 07:18
  • @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` )) – gavenkoa Feb 24 '17 at 10:11
  • @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. – Chad Feb 25 '17 at 07:16
  • 1
    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. – Volksman Nov 01 '17 at 18:54
  • @Chad Thanks for sharing! I have forked your project and modified for Tomcat 9 https://github.com/zepernick/tomcat-classloader – Paul Zepernick Dec 07 '17 at 15:56
  • @PaulZepernick Thanks that's great to hear! – Chad Dec 11 '17 at 07:21
8

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()
Fakrudeen
  • 5,778
  • 7
  • 44
  • 70
7

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

Johannes Ernst
  • 3,072
  • 3
  • 42
  • 56
Nick
  • 5,765
  • 5
  • 27
  • 36
  • 3
    No need for to make own `WebAppLoader` as Tomcat 7 come with `VirtualWebappLoader` which provide desired feature through `virtualClasspath` property. – gavenkoa Sep 30 '14 at 16:58