mvn package 가 만드는 JAR 에는 프로젝트가 컴파일한 클래스만 들어간다. 의존성 라이브러리는 빠져 있으므로 그대로 java -jar 로 띄우면 NoClassDefFoundError 가 난다. 의존성을 함께 담은 이른바 fat JAR(uber JAR)를 만드는 방법은 둘이고, 거기에 폐쇄망이 겹치면 플러그인 자체를 못 받아 빌드가 시작조차 되지 않는 문제가 더해진다.
의존성을 풀어서 하나의 JAR 로 합친다. 설정이 짧고 실행 가능한 JAR 를 만드는 것이 목적이라면 이쪽이 단순하다.
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.example.MainClass</mainClass>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
ServicesResourceTransformer 를 빼면 안 된다. 여러 라이브러리가 각자 META-INF/services/ 에 같은 이름의 파일을 두는 경우(JDBC 드라이버, SLF4J 바인딩, Hadoop FileSystem 구현 등) 뒤에 풀린 것이 앞의 것을 덮어써서, 빌드는 성공하는데 실행 시점에 구현을 못 찾는 상태가 된다. 서명된 JAR 가 섞여 있으면 서명 파일도 걷어내야 한다.
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
배포 묶음의 구조를 직접 정하고 싶을 때 쓴다. 단순히 의존성만 합칠 것이라면 내장 기술자 하나로 끝난다.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.7.1</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>com.example.MainClass</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
JAR 는 합치지 않고 lib/ 에 따로 두는 배포 묶음이 필요하면 기술자를 직접 쓴다. src/main/assembly/dist.xml 로 두고 <descriptor> 로 가리킨다.
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3">
<id>dist</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>true</includeBaseDirectory>
<dependencySets>
<dependencySet>
<scope>runtime</scope>
<outputDirectory>lib</outputDirectory>
<unpack>false</unpack>
</dependencySet>
</dependencySets>
<fileSets>
<fileSet>
<directory>src/main/bin</directory>
<outputDirectory>bin</outputDirectory>
<fileMode>0755</fileMode>
</fileSet>
</fileSets>
</assembly>
<unpack>false</unpack> 면 JAR 를 그대로 lib/ 에 넣고, true 면 풀어서 합친다. 풀어서 합칠 때는 shade 와 같은 리소스 충돌 문제가 생기지만 assembly 쪽에는 transformer 가 없다. 의존성을 풀어 합쳐야 한다면 assembly 가 아니라 shade 를 쓴다.
저장소에 없는 JAR 를 프로젝트에 끼워 넣을 때 system 스코프를 쓰고 싶어지는데, 이 방법은 문제를 부른다.
<!-- 권장하지 않는다 -->
<dependency>
<groupId>com.example</groupId>
<artifactId>legacy-lib</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/legacy-lib-1.0.jar</systemPath>
</dependency>
세 가지가 걸린다.
system 스코프는 오래전에 폐기 예정으로 표시됐다. 최신 Maven 은 경고를 내고, systemPath 가 프로젝트 디렉터리 안을 가리키면 systemPath for ... should not point at files within the project directory 경고가 붙는다.system 스코프 의존성을 결과물에 넣지 않는다. "종속된 jar 를 못 만든다"는 증상 대부분이 여기서 나온다.바른 방법은 그 JAR 를 로컬 저장소에 설치해 보통의 의존성으로 만드는 것이다.
mvn install:install-file \
-Dfile=./lib/legacy-lib-1.0.jar \
-DgroupId=com.example \
-DartifactId=legacy-lib \
-Dversion=1.0 \
-Dpackaging=jar
<dependency>
<groupId>com.example</groupId>
<artifactId>legacy-lib</artifactId>
<version>1.0</version>
</dependency>
여러 사람이 빌드하는 프로젝트라면 사설 저장소에 올린다. 팀원마다 install-file 을 돌리게 하는 것은 결국 같은 이식성 문제를 사람 손으로 미루는 것이다.
폐쇄망에서는 소스가 아니라 플러그인과 의존성 아티팩트를 먼저 옮겨야 한다. Maven 은 플러그인도 저장소에서 내려받으므로, maven-shade-plugin 이 로컬에 없으면 -o 를 줘도 빌드가 실패한다.
가장 확실한 순서는 다음과 같다.
~/.m2/repository 에 쌓인다.~/.m2/repository 를 통째로 압축해 옮긴다.~/.m2/repository 에 푼다. 경로 구조를 그대로 유지해야 한다.mvn -o clean package
dependency:go-offline 으로 미리 받아 두는 방법도 있지만, 빌드 중에만 확인되는 플러그인 의존성이 누락되는 경우가 있어 한 번 실제로 빌드해 보는 쪽이 확실하다.
mvn dependency:go-offline
Nexus 같은 사설 저장소를 폐쇄망 안에 두었다면 ~/.m2/settings.xml 에 미러를 걸어 모든 요청이 그쪽을 거치게 한다. 이 경우 -o 없이 평소대로 빌드한다.
IDE 는 자체 번들 Maven 을 쓰는 경우가 많다. IntelliJ 라면 Settings → Build, Execution, Deployment → Build Tools → Maven 에서 Maven home path 와 Local repository 가 옮겨 둔 경로를 가리키는지, Work offline 이 켜져 있는지 확인한다. 의존성이 External Libraries 에 보이지 않으면 대개 여기가 어긋나 있다.
Windows 에서 다른 프로세스가 파일을 사용 중이기 때문에 프로세스가 액세스할 수 없습니다 가 나오면 target/ 안의 JAR 를 다른 프로세스가 잡고 있는 것이다. IDE 가 백그라운드에서 인덱싱 중이거나, 앞서 띄운 애플리케이션이 그 JAR 를 물고 있거나, 백신이 검사 중인 경우다. clean 을 붙여 다시 돌리고, 그래도 나면 해당 JAR 를 쓰는 프로세스를 먼저 끝낸다.
mvn -o clean package