Maven의 기초를 다루는 초급 튜토리얼은 이 기사의 참고자료 섹션에 소개된 일부 튜토리얼을 비롯하여 많이 있으므로 이 기사의 5가지 팁에서는 그 이후의 단계 즉, Maven을 사용하여 애플리케이션의 라이프사이클을 관리할 때 발생하는 프로그래밍 시나리오와 관련된 정보를 제공한다.
Maven을 사용하면 JAR 파일을 매우 쉽게 빌드할 수 있다. 프로젝트 패키지를 "jar"로 정의한 다음 패키지 라이프사이클 단계를 실행하기만 하면 된다. 하지만 실행 가능 JAR 파일을 정의하는 작업은 좀 까다롭다. 이를 효과적으로 수행하려면 다음 단계를 따른다.
- 실행 가능 클래스를 정의한 JAR의 MANIFEST.MF 파일에서
main
클래스를 정의한다. (MANIFEST.MF는 Maven에서 애플리케이션을 패키징할 때 생성되는 파일이다.) - 프로젝트에 종속된 모든 라이브러리를 찾는다.
- 애플리케이션 클래스에서 찾을 수 있도록 해당 라이브러리를 MANIFEST.MF 파일에 포함시킨다.
이 모든 작업을 수동으로 수행할 수도 있지만 maven-jar-plugin
및 maven-dependency-plugin
이라는 두 Maven 플러그인을 사용하여 효율적으로 수행할 수도 있다.
maven-jar-plugin
에는 많은 기능이 있지만 여기에서는 이 플러그인을 사용하여 기본 MANIFEST.MF 파일의 내용을 수정하는 방법만 살펴본다. POM 파일의 플러그인 섹션에 Listing 1의 코드를 추가한다.
Listing 1. maven-jar-plugin을 사용하여 MANIFEST.MF 수정하기
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <mainClass>com.mypackage.MyClass</mainClass> </manifest> </archive> </configuration> </plugin> |
모든 Maven 플러그인은 <configuration>
요소를 통해 해당 구성을 노출한다. 이 예제에서는 maven-jar-plugin
이 MANIFEST.MF 파일의 내용을 제어하는 archive
속성 구체적으로 말해서, 아카이브의 manifest
속성을 수정한다. 이 속성에는 다음 세 가지 요소가 있다.
addClassPath
: 이 요소를 true로 설정하면maven-jar-plugin
이Class-Path
요소를 MANIFEST.MF 파일에 추가하고 해당Class-Path
요소의 모든 종속 항목을 포함시킨다.classpathPrefix
: 모든 종속 항목을 JAR과 같은 디렉토리에 포함시킬 계획이라면 이 요소를 생략할 수 있다. 그렇지 않은 경우에는classpathPrefix
를 사용하여 모든 종속 JAR 파일의 접두부를 지정한다. Listing 1에서classpathPrefix
는 모든 종속 항목이 아카이브의 "lib
" 폴더에 있어야 한다고 지정한다.mainClass
: 이 요소를 사용하여 사용자가java -jar
명령으로 JAR을 실행할 때 실행할 클래스의 이름을 정의한다.
이러한 세 요소를 사용하여 MANIFEST.MF 파일을 구성한 후에는 모든 종속 항목을 lib
폴더에 실제로 복사해야 한다. 이를 위해maven-dependency-plugin
을 사용한다(Listing 2 참조).
Listing 2. maven-dependency-plugin을 사용하여 lib에 종속 항목 복사하기
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>copy</id> <phase>install</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputDirectory> ${project.build.directory}/lib </outputDirectory> </configuration> </execution> </executions> </plugin> |
maven-dependency-plugin
에는 사용자가 선택한 디렉토리에 종속 항목을 복사하는 copy-dependencies
goal이 있다. 이 예제에서는build
디렉토리 아래의 lib
디렉토리(project-home/target/lib
)에 종속 항목을 복사했다.
종속 항목과 수정된 MANIFEST.MF를 배치한 후에는 다음과 같이 간단한 명령으로 애플리케이션을 시작할 수 있다.
java -jar jarfilename.jar |
maven-jar-plugin
을 사용하여 MANIFEST.MF
파일의 일반적인 부분을 수정할 수 있기는 하지만 MANIFEST.MF를 사용자 정의해야 하는 경우도 있다. 이 작업은 다음과 같이 2단계로 수행할 수 있다.
- 모든 사용자 정의 구성을 "임시" MANIFEST.MF 파일에 정의한다.
- MANIFEST.MF 파일을 사용하고 Maven 사용자 정의를 통해 확장할 수 있도록
maven-jar-plugin
을 구성한다.
예를 들어, Java 에이전트가 포함된 JAR 파일이 있다. Java 에이전트를 실행하려면 Premain-Class
와 권한을 정의해야 한다. Listing 3에서는 그러한 MANIFEST.MF 파일의 내용을 보여 준다.
Listing 3. 사용자 정의 MANIFEST.MF 파일에 있는 Premain-Class 정의
Manifest-Version: 1.0 Premain-Class: com.geekcap.openapm.jvm.agent.Agent Can-Redefine-Classes: true Can-Retransform-Classes: true Can-Set-Native-Method-Prefix: true |
Listing 3에서는 클래스를 재정의 및 변환할 수 있는 권한을 Premain-Class
com.geekcap.openapm.jvm.agent.Agent
에 부여한다. 그런 다음 MANIFEST.MF 파일을 포함하도록 maven-jar-plugin
을 업데이트한다(Listing 4 참조).
Listing 4. Premain-Class 포함시키기
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifestFile> src/main/resources/META-INF/MANIFEST.MF </manifestFile> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>lib/</classpathPrefix> <mainClass> com.geekcap.openapm.ui.PerformanceAnalyzer </mainClass> </manifest> </archive> </configuration> </plugin> |
이 예제에서는 JAR 파일을 Java 에이전트로 실행할 수 있도록 허용하는Premain-Class
도 정의하고 실행 가능 JAR 파일로 실행할 수 있도록 허용하는 mainClass
도 있다는 점이 흥미롭다. 이 특별한 예제에서는OpenAPM
(필자가 빌드한 코드 추적 도구)을 사용하여 Java 에이전트 및 사용자 인터페이스에 의해 기록될 코드 추적을 정의한다. 이렇게 하면 기록된 추적을 효율적으로 분석할 수 있다. 간단히 말해서 이 예제에서는 명시적 매니페스트 파일과 동적 수정 사항을 결합하여 얻을 수 있는 효과를 보여 준다.
Maven의 가장 유용한 기능 중 하나는 종속 항목 관리 기능이다. 사용자가 애플리케이션에 종속된 라이브러리를 정의하기만 하면 Maven이 로컬 또는 중앙 저장소에 있는 종속 항목을 찾아서 다운로드한 후 해당 종속 항목을 사용하여 코드를 컴파일한다.
가끔씩 특정 종속 항목의 원본을 알아야 하는 경우가 있다(예를 들어, 빌드에 동일한 JAR 파일의 호환되지 않는 다양한 버전이 있는 경우). 이 경우에는 JAR 파일의 한 버전이 빌드에 포함되지 않도록 해야 한다. 하지만 먼저 JAR을 가지고 있는 종속 항목을 찾아야 한다.
다음 명령을 알고 있다면 종속 항목을 매우 쉽게 찾을 수 있다.
mvn dependency:tree |
dependency:tree
인수는 모든 직접 종속 항목과 함께 모든 하위 종속 항목을 보여 준다. 예를 들어, Listing 5는 필자의 종속 항목 중 하나에 필요한 클라이언트 라이브러리에서 발췌한 내용이다.
[INFO] ------------------------------------------------------------------------ [INFO] Building Client library for communicating with the LDE [INFO] task-segment: [dependency:tree] [INFO] ------------------------------------------------------------------------ [INFO] [dependency:tree {execution: default-cli}] [INFO] com.lmt.pos:sis-client:jar:2.1.14 [INFO] +- org.codehaus.woodstox:woodstox-core-lgpl:jar:4.0.7:compile [INFO] | \- org.codehaus.woodstox:stax2-api:jar:3.0.1:compile [INFO] +- org.easymock:easymockclassextension:jar:2.5.2:test [INFO] | +- cglib:cglib-nodep:jar:2.2:test [INFO] | \- org.objenesis:objenesis:jar:1.2:test |
Listing 5를 보면 sis-client
프로젝트에 woodstox-core-lgpl
및 easymockclassextension
라이브러리가 필요하다는 것을 알 수 있다. 그런 다음 easymockclassextension
라이브러리에는 cglib-nodep
및 objenesis
라이브러리가 필요하다. 만일 objenesis
에 문제가 있으면, 예를 들어, 1.2와 1.3이라는 두 개의 버전이 있으면 1.2 아티팩트가 easymockclassextension
라이브러리를 통해 간접적으로 반입되었다는 메시지가 이 종속 항목 트리에 표시된다.
dependency:tree
인수를 사용하면 빌드 문제점을 디버깅하는 시간을 많이 줄일 수 있으므로 적극적으로 활용하면 좋을 것이다.
대부분의 실제 프로젝트에는 개발, 품질 보증(QA), 통합 및 프로덕션과 관련된 태스크로 구성된 핵심 환경 그룹이 하나 이상 있다. 그러한 모든 환경을 관리할 때 어려운 부분은 빌드를 구성하는 작업이다. 즉, 올바른 데이터베이스에 연결하고, 올바른 스크립트 세트를 실행하고, 각 환경에 올바른 모든 아티팩트를 배치해야 한다. Maven 프로파일을 사용하면 개별적으로 각 환경에 대한 명시적 지시사항을 빌드하지 않고도 이 모든 작업을 수행할 수 있다.
핵심은 환경 관련 프로파일과 태스크 지향적 프로파일을 결합하는 것이다. 각 환경 프로파일에서는 특정 위치, 스크립트 및 서버를 정의한다. 따라서 이 예제의 pom.xml 파일에서는 태스크 지향적 프로파일인 "deploywar
"를 정의한다(Listing 6 참조).
<profiles> <profile> <id>deploywar</id> <build> <plugins> <plugin> <groupId>net.fpic</groupId> <artifactId>tomcat-deployer-plugin</artifactId> <version>1.0-SNAPSHOT</version> <executions> <execution> <id>pos</id> <phase>install</phase> <goals> <goal>deploy</goal> </goals> <configuration> <host>${deploymentManagerRestHost}</host> <port>${deploymentManagerRestPort}</port> <username>${deploymentManagerRestUsername}</username> <password>${deploymentManagerRestPassword}</password> <artifactSource> address/target/addressservice.war </artifactSource> </configuration> </execution> </executions> </plugin> </plugins> </build> </profile> </profiles> |
ID "deploywar
"로 식별되는 이 프로파일은 특정 사용자 이름 및 비밀번호 신임 정보를 사용하여 특정 호스트 및 포트에 연결하도록 구성되어 있는 tomcat-deployer-plugin
을 실행한다. 이 모든 정보는 변수를 사용하여 정의된다(예: ${deploymentmanagerRestHost}
). 이러한 변수는 각 환경의 profiles.xml 파일에 정의되어 있다(Listing 7 참조).
<!-- Defines the development deployment information --> <profile> <id>dev</id> <activation> <property> <name>env</name> <value>dev</value> </property> </activation> <properties> <deploymentManagerRestHost>10.50.50.52</deploymentManagerRestHost> <deploymentManagerRestPort>58090</deploymentManagerRestPort> <deploymentManagerRestUsername>myusername</deploymentManagerRestUsername> <deploymentManagerRestPassword>mypassword</deploymentManagerRestPassword> </properties> </profile> <!-- Defines the QA deployment information --> <profile> <id>qa</id> <activation> <property> <name>env</name> <value>qa</value> </property> </activation> <properties> <deploymentManagerRestHost>10.50.50.50</deploymentManagerRestHost> <deploymentManagerRestPort>58090</deploymentManagerRestPort> <deploymentManagerRestUsername> myotherusername </deploymentManagerRestUsername> <deploymentManagerRestPassword> myotherpassword </deploymentManagerRestPassword> </properties> </profile> |
Listing 7의 profiles.xml에서는 두 개의 프로파일을 정의한 다음 env
(environment) 특성의 값을 기반으로 프로파일을 활성화했다. env
특성이 dev
로 설정된 경우에는 개발 배치 정보가 사용되며, env
특성이 qa
로 설정된 경우에는 QA 배치 정보가 사용된다. 이처럼 특성 값에 따라 사용되는 정보가 달라진다.
다음은 파일을 배치하는 명령이다.
mvn -Pdeploywar -Denv=dev clean install |
-Pdeploywar
플래그는 Maven에게 deploywar
프로파일을 명시적으로 포함하도록 지시한다. -Denv=dev
명령문은 env
라는 시스템 특성을 작성하고 그 값을 dev
로 설정한다. 이렇게 하면 개발 구성이 활성화된다. -Denv=qa
를 전달하면 QA 구성이 활성화된다.
Maven에는 사용자가 자유롭게 사용할 수 있는 수십 개의 사전 빌드된 플러그인이 있지만 어떤 경우에는 사용자 정의 플러그인이 필요할 수도 있다. 사용자 정의 Maven 플러그인은 다음과 같이 쉽게 빌드할 수 있다.
- POM 패키지 세트를 "
maven-plugin
"으로 설정한 새 프로젝트를 작성한다. - 노출된 플러그인 goal을 정의한
maven-plugin-plugin
에 대한 호출을 포함시킨다. - Maven 플러그인 "
mojo
" 클래스(AbstractMojo
를 확장한 클래스)를 작성한다. - Goal을 정의하는 클래스와 구성 매개변수로 노출할 변수에 대한 Javadoc 주석을 작성한다.
- 플러그인이 호출될 때 호출할
execute()
메소드를 구현한다.
예를 들어, Listing 8에서는 Tomcat을 배치하기 위해 설계된 사용자 정의 플러그인의 관련 부분을 보여 준다.
Listing 8. TomcatDeployerMojo.java
package net.fpic.maven.plugins; import java.io.File; import java.util.StringTokenizer; import net.fpic.tomcatservice64.TomcatDeploymentServerClient; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import com.javasrc.server.embedded.CommandRequest; import com.javasrc.server.embedded.CommandResponse; import com.javasrc.server.embedded.credentials.Credentials; import com.javasrc.server.embedded.credentials.UsernamePasswordCredentials; import com.javasrc.util.FileUtils; /** * Goal that deploys a web application to Tomcat * * @goal deploy * @phase install */ public class TomcatDeployerMojo extends AbstractMojo { /** * The host name or IP address of the deployment server * * @parameter alias="host" expression="${deploy.host}" @required */ private String serverHost; /** * The port of the deployment server * * @parameter alias="port" expression="${deploy.port}" default-value="58020" */ private String serverPort; /** * The username to connect to the deployment manager (if omitted then the plugin * attempts to deploy the application to the server without credentials) * * @parameter alias="username" expression="${deploy.username}" */ private String username; /** * The password for the specified username * * @parameter alias="password" expression="${deploy.password}" */ private String password; /** * The name of the source artifact to deploy, such as target/pos.war * * @parameter alias="artifactSource" expression=${deploy.artifactSource}" * @required */ private String artifactSource; /** * The destination name of the artifact to deploy, such as ROOT.war. * If not present then the * artifact source name is used (without pathing information) * * @parameter alias="artifactDestination" * expression=${deploy.artifactDestination}" */ private String artifactDestination; public void execute() throws MojoExecutionException { getLog().info( "Server Host: " + serverHost + ", Server Port: " + serverPort + ", Artifact Source: " + artifactSource + ", Artifact Destination: " + artifactDestination ); // Validate our fields if( serverHost == null ) { throw new MojoExecutionException( "No deployment host specified, deployment is not possible" ); } if( artifactSource == null ) { throw new MojoExecutionException( "No source artifact is specified, deployment is not possible" ); } ... } } |
클래스 헤더에서 @goal
주석은 이 MOJO가 실행될 것이라는 goal을 지정하며, @phase
는 goal이 실행될 단계를 지정한다. 노출된 각 특성에는 매개변수를 실행할 때 사용되는 별명과 함께 실제 값이 포함된 시스템 특성에 맵핑하는 표현식을 지정하는 @parameter
어노테이션이 있다. 특성에 @required
어노테이션이 있다는 것은 필수 특성임을 의미한다. default-value
가 있는 경우에는 지정된 값이 없을 때 이 값이 사용된다. execute()
메소드에서 getLog()
를 호출하여 Maven 로거에 액세스할 수 있다. Maven 로거는 로깅 레벨에 따라 지정된 메시지를 표준 출력 장치에 출력한다. 플러그인이 실행되지 않으면 MojoExecutionException
이 발생하면서 빌드가 실패한다.
Maven을 빌드 작업에만 사용할 수도 있지만 Maven은 프로젝트 라이프사이클 관리 도구로서도 매우 뛰어나다. 이 기사에서는 잘 알려져 있지는 않지만 Maven을 더욱 효과적으로 사용되는 데 도움이 되는 5가지 기능을 살펴보았다. Maven에 대한 자세한 정보는 참고자료 섹션을 참조한다.
5가지 사항 시리즈의 다음 주제는 Swing으로 아름다운 사용자 인터페이스를 빌드하는 데 도움이 되는 5가지 팁이므로 다음 기사에서도 만나볼 수 있기를 바란다.
'빌드서버 > MAVEN' 카테고리의 다른 글
[maven] pom.xml 에 ${project.basedir} 사용하기 (0) | 2017.10.13 |
---|---|
[Maven] Dependency 의 scope 옵션 (0) | 2017.10.13 |
[Maven] Maven Build 시에 인코딩 변환 (0) | 2017.10.13 |
[Maven] systemPath, system scope 대신에 3rd 라이브러리 추가법 (0) | 2017.10.13 |
[Maven] 메이븐 빌드 - 개발 / 운영 서버 별 빌드 소스 구분 (0) | 2017.06.02 |