Springing into Jars

While the powerful Spring Framework is commonly used for Java web applications, there no reason you can’t also use it for good old-fashioned command-line apps as well.

I recently had occasion to take a large web application and attempt to break a bit out of it to run as a standalone jar, and made a few interesting discoveries along the way.

Let’s say you want to run the following main method:

<code>
package com.point2;

import com.point2.MyBeanClass;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyMainClass {

    public static void main(String args[]) {
         try {
             MyBeanClass myClass = getMyBean("beanId");
             myClass.doImportantWork();
         } catch (Exception e) {
             e.printStackTrace();
         }
    }

    private static MyBeanClass getMyBean(String beanId) {
        ApplicationContext applicationContext =
            new ClassPathXmlApplicationContext("classpath:spring-beans.xml");
        return (MyBeanClass) applicationContext.getBean(beanId);
    }
}
</code>

This class simply creates the Spring application context, gets a bean, and executes the doImportantWork method on it. As it’s a main method, we can call it from the command-line, with no container or deployment required, if we could put our webapp into an executable jar, along with all it’s dependencies (which include Spring).

As I was lucky enough to have a Maven project for my webapp, my first thought was to use the Maven “assembly” plugin to create a single jar file with all required classes. This worked fine, creating my webapp-1.0-jar-with-dependencies.jar as advertised. The problem came when I tried to run my command-line app. I created a little script like so:

<code>
java -cp target/webapp-1.0-jar-with-dependencies.jar com.point2.MyMainClass
</code>

And executed it. Immediately Spring complained about not being able to find NamespaceHandlers for some obscure namespace I used in my Spring configuration files.

Hmph.

After a long dig through the relevant doc, it turns out that several of the Spring jar files have a file called “spring.schemas” in their META-INF directories, and a corresponding file called “spring.handlers” that says where to get the appropriate handlers for the schemas they understand. Unfortunately, the files are not the same in all of the various Spring dependency jars – in other words, the schemas and handlers in one Spring jar are different from the schemas and handlers in another.

When the assembly plugin puts all these jars together into one uber-jar, the “last man in wins”, in other words, whatever Spring jar is processed last has it’s spring.schema and spring.handlers file end up in the uber-jar, overwriting the others.

This results in Spring not being able to find the proper list of handlers on startup, hence my kaboom.

There’s an open bug discussing this problem in the Jira for the assembly plugin, but fortunately there’s another option: The maven “shade” plugin.

Add the following plugin magic to your POM’s build section:

<code>
 <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
            <configuration>
                <transformers>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                        <resource>META-INF/spring.handlers</resource>
                    </transformer>
                    <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                        <resource>META-INF/spring.schemas</resource>
                    </transformer>
                </transformers>
            </configuration>
        </execution>
    </executions>
</plugin>

</code>

The “AppendingTransformer” bit is where we tell the plugin to concatenate together all resources with the same name, instead of just using the most recent one encountered.

Then change the element in your project’s POM to say “jar” instead of “war”, and do a

<code>
mvn package
</code>

Now you end up with a jar file called “webapp-1.0.jar” instead of the war file in your target directory, but this time with all the proper spring.schema and spring.handler files appended together.

Now you can say

<code>
java -cp target/webapp-1.0.jar com.point2.MyMainClass
</code>

And Spring happily winds up an application context and works as expected.

Happy jar-ing!

Published: May 23 2009