들어가기 앞서
가령 아래와 같은 구조의 project-a, project-b, project-c 라고 하는
3개의 프로젝트가 있다고 하자. (클릭하면 크게 보임)
이때 'MainClass' 가 있는 'project-a'의 pom.xml의 'dependency' 설정은 아래와 같이 구성한다.
<dependencies> <dependency> <groupId>com.asuraiv.bbb</groupId> <artifactId>project-b</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> </dependency> </dependencies>
간단히 코드도 살펴보자.
'project-a'의 'MainClass'는 아래처럼 'project-b'의
'com.asuraiv.bbb.util_1.B_Utils1' 클래스를 사용한다.
package com.asuraiv.aaa; import com.asuraiv.bbb.util_1.B_Utils1; public class MainClass { public static void main(String[] args) { System.out.println("Hello Shade Plugin !!"); // project-b 의 Utils 클래스 사용 B_Utils1.printString("Hello Project B"); } }
'B_Utils1.printString' 메소드는 아래처럼 단순히 System out으로 문자열을 찍는다.
package com.asuraiv.bbb.util_1; public class B_Utils1 { public static void printString(String text) { System.out.println(text); } }
한편, 'com.asuraiv.bbb.util_2.B_Utils2' 클래스는 'project-c' 의 클래스를 사용한다.
package com.asuraiv.bbb.util_2; import com.asuraiv.ccc.util_2.C_Utils2; public class B_Utils2 { public static void printIntegerUsingCUtils(int value) { // project-c 의 Utils 클래스 사용 C_Utils2.printInteger(value); } }뭐, 이런 상황이다.
이렇게 되면 이들 3개의 프로젝트의 의존관계는 아래처럼 될 것이다.
정리하자면 'project-a' 의 'MainClass' 에서 'project-b'의 'com.asuraiv.bbb.util_1.B_Utils1' 를 사용하고, 'project-b' 의 다른 패키지인 'com.asuraiv.bbb.util_2.B_Utils2'에서는 'project-c'의 'com.asuraiv.ccc.util_2.C_Utils2' 를 사용하는 것이다.
이때 'project-a'를 shade 플러그인을 사용하여 'uber-jar' 만들고,
디렉토리 구조를 보면 아래와 같은 것이다.
├─com │ └─asuraiv │ ├─aaa │ ├─bbb │ │ ├─util_1 │ │ └─util_2 │ └─ccc │ ├─util_1 │ └─util_2 ├─junit │ ├─awtui │ ├─extensions │ ├─framework │ ├─runner │ ├─swingui │ │ └─icons │ └─textui └─META-INF └─maven ├─com.asuraiv.aaa │ └─project-a ├─com.asuraiv.bbb │ └─project-b └─com.asuraiv.ccc └─project-c
artifactSet
위의 상황들이 전제로 주어졌을때, 결과적으로 'project-c'는 'project-b'가 의존하고 있지만, 사용되지 않는다. 'project-a'의 'MainClass'가 'project-b'의 'com.asuraiv.bbb.util_1.B_Utils1' 만을 사용하고 있기 때문이다.
하지만 project-c를 사용하지 않음에도 불구하고 shade 플러그인을 사용해 생성된 uber-jar는 위의 tree 구조를 보면 알 수 있듯 물리고 물린 의존관계는 몽땅 패키징이 되어 사용되지 않더라도 uber-jar 안에 포함되 있는것을 볼 수 있다.
이때 사용하지 않는 'proejct-c' 의 의존관계를 uber-jar 생성시에 제외시킬 수 있다.
'configuration' 태그 밑의 'artifactSet' 설정을 사용하여 어떤 라이브러리를 제외하고 포함시킬 것인지 정의가 가능하다. 아래와 같이 설정한다.
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.4.3</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.asuraiv.aaa.MainClass</mainClass> </transformer> </transformers> <artifactSet> <excludes> <exclude>com.asuraiv.ccc:project-c</exclude> </excludes> </artifactSet> <outputFile>d:/shade/project-a.jar</outputFile> </configuration> </execution> </executions> </plugin>'artifactSet'태그와 함께 'excludes' 설정을 하면, 어떤 라이브러리를 제외 시킬 것인지 명시 할 수 있게 된다. 더불어 'outputFile'은 jar 파일이 떨어지는 위치를 말한다.
이러한 설정을 바탕으로 package를 하게 되면 아래와 같은 tree 구조가 된다.
├─com │ └─asuraiv │ ├─aaa │ └─bbb │ ├─util_1 │ └─util_2 ├─junit │ ├─awtui │ ├─extensions │ ├─framework │ ├─runner │ ├─swingui │ │ └─icons │ └─textui └─META-INF └─maven ├─com.asuraiv.aaa │ └─project-a └─com.asuraiv.bbb └─project-b보는것과 같이 사용하지 않는 'project-c' 가 패키징 되지 않았다.
filters
하지만 아래처럼 'MainClass'에서 'B_Utils1.printString'을 사용하지 않고 'project-c'를 사용하여 문자열을 찍는 'B_Utils2.printIntegerUsingCUtils' 메소드를 사용한다면 어떨까?
package com.asuraiv.aaa; import com.asuraiv.bbb.util_2.B_Utils2; public class MainClass { public static void main(String[] args) { System.out.println("Hello Shade Plugin !!"); // project-c를 사용하여 문자열을 찍는 project-b의 Utils 클래스를 사용 B_Utils2.printStringUsingCUtils("Hello Project B"); } }'project-c' 라이브러리를 통째로 날렸다가는 프로그램 실행시에 아래와 같은 에러를 볼 것이다.
'MainClass'에서, 'project-c' 라이브러리를 사용하는 'project-b'의 메소드를 호출했는데, 'project-c'가 통째로 제외 되었으니 런타임에 오류가 나는것은 당연하다.
이때 'filter' 설정을 사용해 'project-c' 라이브러리에서 사용하는 패키지를 남기고 나머진 제외 시켜서 uber-jar를 생성할 수 있다!
<configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.asuraiv.aaa.MainClass</mainClass> </transformer> </transformers> <artifactSet> <excludes> <exclude>junit:junit</exclude> </excludes> </artifactSet> <filters> <filter> <artifact>com.asuraiv.ccc:project-c</artifact> <excludes> <exclude>com/asuraiv/ccc/util_2/**</exclude> </excludes> </filter> </filters> <outputFile>d:/project-a.jar</outputFile> </configuration>앞서 배운 'artifactSet' 설정을 사용하여 'junit'을 통째로 제외 시켰다.
여기서 'filters' 설정에 주목하자. 현재 'project-b'의 'printStringUsingCUtils' 메소드는, 'project-c'의 'com.asuraiv.ccc.util_1' 패키지만을 사용하고 'com.asuraiv.ccc.util_2' 패키지는 사용하지 않는다. 따라서 'filters' 설정을 사용하여, 특정 라이브러리의 특정 패키지만을 제외 시킨 것이다.
tree로 디렉토리 구조를 보면 아래와 같이 필요한 패키지만 uber-jar에 포함되어 있다.
├─com │ └─asuraiv │ ├─aaa │ ├─bbb │ │ ├─util_1 │ │ └─util_2 │ └─ccc │ └─util_1 └─META-INF └─maven ├─com.asuraiv.aaa │ └─project-a ├─com.asuraiv.bbb │ └─project-b └─com.asuraiv.ccc └─project-cccc.utils_2 패키지가 제외 되었다. 근데 사실 bbb.utils_1 패키지도 사용하지 않는다.
filter 설정을 추가해보자
<filters> <filter> <artifact>com.asuraiv.bbb:project-b</artifact> <excludes> <exclude>com/asuraiv/bbb/util_1/**</exclude> </excludes> </filter> <filter> <artifact>com.asuraiv.ccc:project-c</artifact> <excludes> <exclude>com/asuraiv/ccc/util_2/**</exclude> </excludes> </filter> </filters>위와같이 'project-b' 에 관련한 필터설정도 추가했다.
빌드후에 디렉토리 구조를 보면 아래와 같이 필요없는 패키지들이 모두
제거된 것을 볼 수 있다.
├─com │ └─asuraiv │ ├─aaa │ ├─bbb │ │ └─util_2 │ └─ccc │ └─util_1 └─META-INF └─maven ├─com.asuraiv.aaa │ └─project-a ├─com.asuraiv.bbb │ └─project-b └─com.asuraiv.ccc └─project-c
minimizeJar
사실 'artifactSet' 이나 'filter' 설정처럼 세밀하게 포함될/제외될 라이브러리나 패키지를 설정하기 힘들다면, 'minimizeJar' 설정으로 간편하게 필요없는 소스코드들을 제외 시킬 수 있다
<configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.asuraiv.aaa.MainClass</mainClass> </transformer> </transformers> <minimizeJar>true</minimizeJar> <!-- 필요없는 소스코드 제외! --> <outputFile>d:/shade/project-a.jar</outputFile> </configuration>위와 같이 'minimizeJar' 태그 설정만 하면 우리 위에서 살펴봤던 'artifactSet'과 'filter'설정을 한 것과 동일한 결과를 가져온다.
여태까지 'exclude'의 경우만 설명하고 'include'의 경우를 설명하지 않았는데, 사용법은 'exclude'와 동일하다.
만약 minimizeJar를 사용했는데 런타임에 필요한 패키지까지 제외되어 오류가 난다면 'include' 설정을 사용하여 필요한 패키지를 포함 시킬 수 있을 것이다.
'artifactSet'을 사용하여 어떤 라이브러리를 포함/제외 시킬 것인지, 'filter'를 사용하여 패키지 레벨로 세세하게 명시할 것인지, minimizeJar를 사용하여 간편하게 jar파일을 만들것인지.. 이 모든 방법들을 적당하게 조합하여 사용하면 효율적인 용량의 jar 파일을 만들 수 있을 것이다.