Статьи

Упаковка вашего Java-приложения в один (или толстый) JAR

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

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

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

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    <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 и выглядит так:

1
2
3
4
5
6
package com.example;
public class SimpleBean {
    public void print() {
        System.out.println( 'Called from single JAR!' );
    }
}

Влюбившись в конфигурацию Spring Java, давайте определим наш контекст как аннотированный AppConfig POJO:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
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();
    }
}

И, наконец, наше приложение Starter с main () :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
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();
    }
}

Добавление нашего основного класса в META-INF / MANIFEST.MF позволяет использовать возможности Java для запуска файла JAR без явного указания класса с помощью метода main () . Плагин Maven JAR может помочь нам в этом.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
<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>

При попытке запустить java -jar spring-one-jar-0.0.1-SNAPSHOT.jar в консоль будет выведено исключение: java.lang.NoClassDefFoundError . Причина довольно проста: даже такое простое приложение, как это, уже требует, чтобы следующие библиотеки находились в пути к классам.

1
2
3
4
5
6
7
8
9
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

Давайте посмотрим, что One-JAR может сделать для нас здесь. Благодаря наличию onejar-maven-plugin мы можем добавить его в раздел плагинов нашего POM-файла.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
<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>

Кроме того, раздел pluginRepositories должен содержать этот репозиторий для загрузки плагина.

1
2
3
4
5
6
<pluginrepositories>
    <pluginrepository>
        <id>onejar-maven-plugin.googlecode.com</id>
    </pluginrepository>
</pluginrepositories>

В результате в целевой папке будет доступен другой артефакт с постфиксом one-jar : spring-one-jar-0.0.1-SNAPSHOT.one-jar.jar . Запуск этого с java -jar spring-one-jar-0.0.1-SNAPSHOT.one-jar.jar выведет на консоль:

1
Called from single JAR!

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

Спасибо One-JAR, ребята!

Ссылка: простая, но мощная концепция: упаковка вашего Java-приложения в один (или толстый) JAR от нашего партнера по JCG Андрея Редько в блоге Андрея Редько {devmind} .