Today's post will target an interesting but quite powerful concept: packing your application as single, runnable JAR file, also known as one or fat JAR.
We get used to large WAR archives which contain all dependencies packed together under some common folder structure. With JAR-like packaging the story is a bit different: in order to make your application runnable (via java -jar) all dependencies should be provided over classpath parameter or environment variable. Usually it means there would be some lib folder with all dependencies and some runnable script which will do the job to construct classpath and run JVM. Maven Assembly plugin is well know for making such kind of application distribution.
A slightly different approach would be to package all your application dependencies to the same JAR file and make it runnable without any additional parameters or scripting required. Sounds great but ... it won't work unless you add some magic: meet One-JAR project.
Let's briefly outline the problem: we are writing a stand-alone Spring application which should be runnable just by typing java -jar <our-app.jar>.
As always, let's start with our POM file, which will be pretty simple
4.0.0 com.example spring-one-jar 0.0.1-SNAPSHOT jar spring-one-jar http://maven.apache.org UTF-8 3.1.1.RELEASE cglib cglib-nodep 2.2 org.springframework spring-core ${org.springframework.version} org.springframework spring-context ${org.springframework.version}
Our sample application will bootstrap Spring context, get some bean instance and call a method on it. Our bean is called SimpleBean and looks like:
package com.example; public class SimpleBean { public void print() { System.out.println( "Called from single JAR!" ); } }
Falling in love with Spring Java configuration, let us define our context as annotated AppConfig POJO:
package com.example.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.example.SimpleBean; @Configuration public class AppConfig { @Bean public SimpleBean simpleBean() { return new SimpleBean(); } }
And finally, our application Starter with main():
package com.example; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import com.example.config.AppConfig; public class Starter { public static void main( final String[] args ) { ApplicationContext context = new AnnotationConfigApplicationContext( AppConfig.class ); SimpleBean bean = context.getBean( SimpleBean.class ); bean.print(); } }
Adding our main class to META-INF/MANIFEST.MF allows to leverage Java capabilities to run JAR file without explicitly specifying class with main() method. Maven JAR plugin can help us with that.
org.apache.maven.plugins maven-jar-plugin com.example.Starter
Trying to run java -jar spring-one-jar-0.0.1-SNAPSHOT.jar will print the exception to the console: java.lang.NoClassDefFoundError. The reason is pretty straightforward: even such a simple application as this one already required following libraries to be in classpath.
aopalliance-1.0.jar cglib-nodep-2.2.jar commons-logging-1.1.1.jar spring-aop-3.1.1.RELEASE.jar spring-asm-3.1.1.RELEASE.jar spring-beans-3.1.1.RELEASE.jar spring-context-3.1.1.RELEASE.jar spring-core-3.1.1.RELEASE.jar spring-expression-3.1.1.RELEASE.jar
Let's see what One-JAR can do for us here. Thanks to availability of onejar-maven-plugin we can add one to the plugins section of our POM file.
org.dstovall onejar-maven-plugin 1.4.4 0.97 onejar one-jar
Also, pluginRepositories section should contain this repository in order to download the plugin.
onejar-maven-plugin.googlecode.com http://onejar-maven-plugin.googlecode.com/svn/mavenrepo
As the result, there will be another artifact available in the target folder, postfixed with one-jar: spring-one-jar-0.0.1-SNAPSHOT.one-jar.jar. Running this one with java -jar spring-one-jar-0.0.1-SNAPSHOT.one-jar.jar will print to the console:
Called from single JAR!
Fully runnable Java application as single, redistributable JAR file! The last comment: though our application looks pretty simple, One-JAR works perfectly for complex, large applications as well without any issues. Please, add it to your toolbox, it's really useful tool to have.
Thanks to One-JAR guys!
4 comments:
Now that is a crazy idea. What sort of nut would propose something like this?
:-)
On a more serious note, see this concept taken to its logical conclusion:
http://dropwizard.codahale.com/
I just did a small prototype using this as a REST frontend for a Cassandra backend and it was very straightforward...
Thanks a lot for this great comment! I have heard about DropWizard and was going to look at it more closely. Thanks!
Have you had a look at the maven-shade-plugin? Similar mechanism, but without the need for a custom classloader.
br, Sven
Hi Sven! Thank you for pointing out the maven-shade-plugin. I have heard about it (though haven't seen it in the action yet) and I will definitely give it a shot. It would be interesting to compare those two techniques. Thank you very much.
Post a Comment