Статьи

Простая, но мощная концепция: упаковка вашего Java-приложения в один JAR

 

Сегодняшняя статья будет нацелена на интересную, но довольно мощную концепцию: упаковывать ваше приложение в один исполняемый файл JAR, также известный как один или толстый JAR.

Мы привыкаем к большим архивам WAR, которые содержат все зависимости, упакованные вместе в какую-то общую структуру папок. С JAR-подобной упаковкой история немного другая: для того, чтобы сделать ваше приложение работоспособным (через java -jar ), все зависимости должны быть предоставлены через параметр classpath или переменную окружения. Обычно это означает, что там будет какая-нибудь папка lib со всеми зависимостями и какой-нибудь исполняемый скрипт, который будет выполнять работу по созданию classpath и запуску JVM. Плагин Maven Assembly хорошо известен для распространения приложений такого рода.

Несколько иной подход заключается в том, чтобы упаковать все зависимости вашего приложения в один и тот же файл JAR и сделать его работоспособным без каких-либо дополнительных параметров или сценариев. Звучит здорово, но … это не сработает, если вы не добавите немного волшебства: познакомьтесь с проектом One-JAR .

Давайте кратко опишем проблему: мы пишем отдельное приложение Spring, которое можно запустить, просто набрав java -jar <our-app.jar> .

Как всегда, давайте начнем с нашего файла POM, который будет довольно простым

<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.example</groupid>
    <artifactid>spring-one-jar</artifactid>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring-one-jar</name>
    <url>http://maven.apache.org</url>

    <properties>
        <project.build.sourceencoding>UTF-8</project.build.sourceencoding>
        <org.springframework.version>3.1.1.RELEASE</org.springframework.version>
    </properties>

    <dependencies>
        <dependency>
            <groupid>cglib</groupid>
            <artifactid>cglib-nodep</artifactid>
            <version>2.2</version>
        </dependency>

        <dependency>
            <groupid>org.springframework</groupid>
            <artifactid>spring-core</artifactid>
            <version>${org.springframework.version}</version>
        </dependency>

        <dependency>
            <groupid>org.springframework</groupid>
            <artifactid>spring-context</artifactid>
            <version>${org.springframework.version}</version>
        </dependency>
    </dependencies>
</project>

Наше примерное приложение загрузит контекст Spring , получит некоторый экземпляр компонента и вызовет для него метод. Наш компонент называется SimpleBean и выглядит так:

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.

<build>
    <plugins>
        <plugin>
            <groupid>org.apache.maven.plugins</groupid>
            <artifactid>maven-jar-plugin</artifactid>
            <configuration>
                <archive>
                    <manifest>
                        <mainclass>com.example.Starter</mainclass>
                    </manifest>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>

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.

<plugin>
    <groupid>org.dstovall</groupid>
    <artifactid>onejar-maven-plugin</artifactid>
    <version>1.4.4</version>
    <executions>
        <execution>
            <configuration>
                <onejarversion>0.97</onejarversion>
                <classifier>onejar</classifier>
            </configuration>
            <goals>
                <goal>one-jar</goal>
            </goals>
        </execution>
   </executions>
</plugin>

Also, pluginRepositories section should contain this repository in order to download the plugin.

<pluginrepositories>
    <pluginrepository>
        <id>onejar-maven-plugin.googlecode.com</id>
        <url>http://onejar-maven-plugin.googlecode.com/svn/mavenrepo</url>
    </pluginrepository>
</pluginrepositories>

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!