To create an executable standalone jar, which can be run by command like java –jar myExecutable.jar, there are 2 conditions need to be fulfilled:
- Specify the entry main class in META-INFO/MANIFEST.MF file
- Include all dependences in the final jar file
The second condition is not needed theoretically, but in practical will always be necessary.
If the project uses Spring with xml configuration, then there’s one more condition to fulfill.
- Handle spring schemas for different spring packages. (see below for explanation)
The best maven plugin to handle all there three requirements are maven-shade-plugin. (some other plugin such as maven-assembly-plugin can deal with the first two requirements, but not the third one).
In this article, a hello world spring project will be package to an executable standalone jar file using maven. The way used in this article can also perfectly create executable standalone jar package, even if the project don’t use spring.
0. What you need
- JDK
- Spring
- Maven 3.2.1
1. Define the class
There are 2 classes. The first one the a spring bean.
package com.shengwang.demo;
import org.springframework.stereotype.Component;
@Component
public class HelloWorldBean {
public void sayHello(String name) {
System.out.println("Hello "+name);
}
}
The second one is the main class, which get the hello world bean and call its sayHello method.
package com.shengwang.demo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class DemoMain {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("application-context.xml");
HelloWorldBean bean = ctx.getBean(HelloWorldBean.class);
bean.sayHello("Spring");
((ClassPathXmlApplicationContext) (ctx)).close();
}
}
2. Spring configuration
The spring xml configuration file application-context.xml is also very simple.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd">
<context:component-scan base-package="com.shengwang.demo" />
</beans>
3. Maven pom
Plugin maven-shade-plugin in the pom file is the key of this demo.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.shengwang.demo</groupId>
<artifactId>spring-executable-standalone</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-executable-standalone</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.0.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Use JDK 7 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
<!-- =============================================================== -->
<!-- use shade plugin to package spring project into executable jar -->
<!-- =============================================================== -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<!-- =================================================== -->
<!-- define the main entry for the output jar file -->
<!-- =================================================== -->
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.shengwang.demo.DemoMain</mainClass>
</transformer>
<!-- =================================================== -->
<!-- append all spring.handlers instead of overwriting -->
<!-- =================================================== -->
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.handlers</resource>
</transformer>
<!-- =================================================== -->
<!-- append all spring.schemas instead of overwriting -->
<!-- =================================================== -->
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/spring.schemas</resource>
</transformer>
</transformers>
<!-- ============================================================ -->
<!-- exclude any digital signiture file from third party jar file -->
<!-- ============================================================ -->
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
In the plugin, 3 transformers and 1 filter are used. Let’s explain them one by one.
The first transformer is ManifestResourceTransformer, which will modify the META-INFO/MANIFEST.MF file in the final output jar file. This transformer is used to add Main-Class entry in the MANIFEST.MF file. If unzip the output jar, open the META-INFO/MANIFEST.MF file, we can see the added line.
The second and third transformers are AppendingTransformer. Why these two transformers are needed? Because every Spring package store xsd mapping file in the same direcotry hierarchy, /META-INF/spring.schemas and /META-INF/spring.handlers, see example below.
For example two spring artifacts, spring-aop and spring-beans, have the exactly same files in the same directory. When using maven to package jar, these files from different dependency packages are combined in to one file by the transformers.
The filters are used to exclude the digital signature files out of the final output jar file. Some of the third party dependency may be digitally signed. These signature files must be screened out to prevent signature verification exceptions.
For most cases, the main-class entry is the only thing need to change.
4. Package
Run maven to package the project into one jar file.
mvn clean package
5. Run the output jar
Run the output jar file
java -jar spring-executable-standalone-0.0.1-SNAPSHOT.jar
The output looks like:
Thank you for the post. It Helped me to build single FAT Jar File.
ReplyDeletethanks a lot, it's so helpfull
ReplyDeleteI placed application-context.xml in the same path as HellloWorldBean.java.
ReplyDeleteIs this causing issue? When run the jar from command prompt, i get:
Feb 21, 2017 10:36:45 AM org.springframework.context.support.ClassPathXmlApplica
tionContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationCont
ext@63415de6: startup date [Tue Feb 21 10:36:45 EST 2017]; root of context hiera
rchy
Feb 21, 2017 10:36:46 AM org.springframework.beans.factory.xml.XmlBeanDefinition
Reader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [application-context
.xml]
Exception in thread "main" org.springframework.beans.factory.BeanDefinitionStore
Thanks, was missing part, which includes spring.handlers and spring.schemas.
ReplyDeleteThank you, very useful information.
ReplyDelete