본문 바로가기
Homo Faber/Maven Definitive Guide

3장. 간단한 Maven 프로젝트

by javauser 2008. 10. 29.
3.1 개요

이 장에서 Maven Archetype 플러그인을 사용해서 생성되는 간단한 프로젝트를 소개한다. 이러한 기초적인 적용은 프로젝트 개발에 따라 진행하는 동안 몇가지 핵심 메이븐 개념을 설명할 기회를 제공해준다.
복잡하고, 여러 모듈 빌드에 대한 메이븐 사용을 시작할 수 있기 전에 기본적인 내용으로 시작해야 한다. 이전에 메이븐을 사용했다면, 세부사항을 살펴보는 것이 좋은 일이라는 것을 알 수 있을 것이다. 빌드는 “단지 작업”을 하는 것이며, 기본 행위를 변경하거나 플러그인을 변경하여 작성하고자 할 때에만 메이븐의 세부사항으로 깊게 파고 들어갈 필요가 있다. 하지만, 세부사항을 깊게 파고들어갈 필요가 있을 때 핵심 개념에 대한 전반적인 이해는 필수적이다. 이 장은 가장 단순한 메이븐 프로젝트를 소개하며 메이븐을 확고한 빌드 플랫폼으로 만드는 핵심 개념 몇가지를 제시하는 것을 목적으로 한다. 내용을 읽은 후에, 빌드 생명주기, 메이븐 레파지토리, 의존성 관리, 그리고 POM에 대한 기본적인 이해를 할 수 있을 것이다.

3.1.1 이 장의 예제 다운로드 받기

이 장은 메이븐의 핵심 개념을 살펴보는데 사용되는 매우 단순한 예제를 만들었다. 설명된 대로 단계를 쫓아려면, 메이븐에 만들어진 코드를 다시 생성하기 위해 예제를 다운로드 받을 필요가 없다. 이 단순한 예제를 생성하는 Maven Archetype 플러그인을 사용할 것이며, 이 장에서 어떤 방법이든지 간에 프로젝트를 변경하지 않는다. 최종 예제 소스 코드를 사용해서 이 장으로 읽기 원한다면, 예제 프로젝트는 http://www.sonatype.com/book/mvn-examples-1.0.zip 나 http://www.sonatype.com/book/mvn-examples-1.0.tar.gz 에서 책의 예제 코드와 같이 다운로드 받을 수 있다. 디렉토리에 이 압축파일을 풀고, ch03/ 디렉토리로 이동한다. 거기에서 이 장에 대한 소스 코드를 포함하는 simple/ 라고 하는 디렉토리를 볼 수 있을 것이다. 웹 브라우저에서 예제 코드와 같이 따라하기 원한다면 http://www.sonatype.com/book/examples-1.0 로 이동해서 ch03/ 디렉토리를 클릭하라.

3.2 Simple 프로젝트 생성

새로운 메이븐 프로젝트를 시작하기 위해서 다음과 같이 명령 행에 Maven Archetype 플러그인을 사용한다.

$ mvn archetype:create -DgroupId=org.sonatype.mavenbook.ch03 \
                                         -DartifactId=simple \
                                         -DpackageName=org.sonatype.mavenbook
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'archetype'.              
[INFO] artifact org.apache.maven.plugins:maven-archetype-plugin: checking for \
       updates from central
[INFO] -----------------------------------------------------------------------
[INFO] Building Maven Default Project
[INFO]    task-segment: [archetype:create] (aggregator-style)
[INFO] --------------------------------------------------------------------
[INFO] [archetype:create]
[INFO] artifact org.apache.maven.archetypes:maven-archetype-quickstart: \
       checking for updates from central
[INFO] Parameter: groupId, Value: org.sonatype.mavenbook.ch03
[INFO] Parameter: packageName, Value: org.sonatype.mavenbook
[INFO] Parameter: basedir, Value: /Users/tobrien/svnw/sonatype/examples
[INFO] Parameter: package, Value: org.sonatype.mavenbook
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: artifactId, Value: simple
[INFO] * End of debug info from resources from generated POM *
[INFO] Archetype created in dir: /Users/tobrien/svnw/sonatype/examples/simple

mvn은 Maven2 명령어이다. archetype:create 는 호출되는 메이븐 goal이다. 아파치 앤트에 익숙하다면, 메이븐 goal은 앤트의 target에 비유된다. 둘 다 빌드에서 완료되는 단위 작업을 나타낸다. -Dname=value 는 goal에 넘겨주는 인자이며, 명령행을 통해 JVM에 넘겨주는 시스템 속성 옵션과 유사한 –D 속성 형태를 취한다. archetype:create goal의 목적은 아키타입으로부터 프로젝트를 빠르게 생성하는 것이다. 여기서 아키타입은 "다른 유사한 것들이 패턴화된 후의 원래의 모델이나 유형, 프로토타입"으로 정의된다. 단순한 스윙 어플리케이션에서 복잡한 웹 어플리케이션까지 모든 것에 대해 메이븐에 수많은 아키타입이 가능하다. 이 장에서 단순한 뼈대 시작 프로젝트를 생성하기 위해 가장 기본적인 아키타입을 사용할 것이다. 플러그인은 archetype 접두사이며, goal은 create이다.
일단 프로젝트를 생성했으면, simple 디렉토리에 메이븐이 만들어진 디렉토리 구조를 살펴보자.

simple/<-- (1)
simple/pom.xml<-- (2)
      /src/
      /src/main/<-- (3)
          /main/java
      /src/test/<-- (4)
          /test/java

위의 생성된 디렉토리는 메이븐 표준 디렉토리 구조를 따르고 있다. 이 장의 후반부에 더 자세하게 설명하겠지만, 아직까지는 다음의 몇가지 기본 디렉토리에 대해서 이해하도록 하자.

(1) Maven Archetype 플러그인은 artifactId인 Simple에 일치하는 디렉토리를 생성한다. 이는 프로젝트의 기본 디렉토리로 알려져있다.
(2) 모든 메이븐 프로젝트는 pom.xml 이라고 하는 파일에 프로젝트 객체 모델로 알려진 것을 가진다. 이 파일은 프로젝트를 설명하며, 플러그인을 설정하고, 의존관계를 선언한다.
(3) 프로젝트의 소스 코드와 리소스는 src/main 아래에 위치한다. simple 자바 프로젝트의 경우에서 몇가지 자바 클래스들과 속성 파일들로 구성된다. 또 다른 프로젝트에서 어플리케이션 서버를 위한 웹 어플리케이션이나 설정 파일의 문서 루트가 될 수도 있다. 자바 프로젝트에서 자바 클래스들은 src/main/java 에 위치하며, 클래스패스 자원들은 src/main/resources 에 위치한다.
(4) 프로젝트의 테스트 케이스들은 src/test 에 위치한다. 이 디렉토리 하위에 JUnit이나 TestNG 테스트와 같은 자바 클래스들이 src/test/java 에 위치하며, 테스트를 위한 클래스패스 자원들이 src/test/resources 에 위치한다.

Maven Archetype 플러그인은 메시지를 출력하는 static main 함수를 가지는 13줄짜리 자바 클래스인 org.sonatype.mavenbook.App 클래스 하나를 생성했다.

package org.sonatype.mavenbook;

/**
 * Hello world!
 *
 */
public class App
{
    public static void main( String[] args )
    {
        System.out.println( "Hello World!" );
    }
}

가장 단순한 Maven 아키타입은 표준 출력에 "Hello World!"라고 출력하는 가장 단순한 가능한 프로그램을 생성한다.

3.3 Simple 프로젝트 빌드

이전 절 (3.2절)의 내용에 따라 Maven Archetype 플러그인으로 프로젝트를 생성했다면 어플리케이션을 빌드하고 패키지하기 원할 것이다. 그렇게 하기 위해 pom.xml 을 포함하는 디렉토리에서 mvn install 을 실행한다.

$ mvn install
[INFO] Scanning for projects...
[INFO] -------------------------------------------------------
[INFO] Building simple
[INFO]    task-segment: [install]
[INFO] -------------------------------------------------------
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] Compiling 1 source file to /simple/target/classes
[INFO] [resources:testResources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:testCompile]
[INFO] Compiling 1 source file to /simple/target/test-classes
[INFO] [surefire:test]
[INFO] Surefire report directory: /simple/target/surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running org.sonatype.mavenbook.AppTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.105 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] [jar:jar]
[INFO] Building jar: /simple/target/simple-1.0-SNAPSHOT.jar
[INFO] [install:install]
[INFO] Installing /simple/target/simple-1.0-SNAPSHOT.jar to \
  ~/.m2/repository/org/sonatype/mavenbook/ch03/simple/1.0-SNAPSHOT/ \
  simple-1.0-SNAPSHOT.jar

가장 단순한 메이븐 프로젝트를 생성하고, 컴파일하고, 테스트하고, 패키지하고, 설치했다. 이 프로그램이 작동되는지를 확인하기 위해, 다음의 명령행에서 실행한다.

$ java -cp target/simple-1.0-SNAPSHOT.jar org.sonatype.mavenbook.App
Hello World!


3.4. Simple POM

메이븐이 실행했을 때 프로젝트에 대한 정보에 대해 POM을 보게된다. POM은 이것은 어떤 유형의 프로젝트인가? 이 프로젝트에 대한 어떤 빌드 변경이 있는가?와 같은 질문에 답을 준다. 예제 3-1은 Maven Archetype 플러그인의 create goal에 의해 만들어진 기본 pom.xml 파일을 보여준다.

예제 3-1. Simple 프로젝트의 pom.xml 파일
<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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>org.sonatype.mavenbook.ch03</groupId>
  <artifactId>simple</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>simple</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

이 pom.xml 파일은 메이븐 프로젝트에 대해 다루게 될 가장 기본적인 POM이다. 보통 POM 파일은 여러 의존관계를 정의하고 플러그인 행위를 변경하는 상당히 더 복잡하다. 처음 몇 개의 요소 - <groupId>, <artifactId>, <packaging>, <version> - 들은 프로젝트를 유일하게 식별해주는 메이븐 위상(coordinate)이라 한다. <name> 과 <url> 은 사람이 읽은 수 있는 이름을 제공하며 프로젝트 웹 사이트와 관련시키면서 POM의 설명 요소들이다. 마지막으로 dependencies 요소는 JUnit이라고 하는 단위 테스팅 프레임워크에 대한 단일, 테스트 영역의 의존관계를 정의한다. 이러한 주제들은 다음 절인 3.5 절과 9장에서 소개될 것이다. 이 시점에서 알 필요가 있는 것은 pom.xml 이 메이븐을 진행하게 만드는 파일이라는 것이다.
메이븐은 항상 해당 프로젝트의 pom.xml 로부터 세팅들에 대한 조합인 효율적인 POM, 모든 부모 POM들, 메이븐 내에 정의된 최상위 POM, 사용자 정의 세팅들, 그리고 사용 중인 프로필에 대해 실행한다. 모든 프로젝트들은 궁극적으로 최상위 POM을 확장하는데, 이는 의미있는 기본 설정 세팅들을 정의하며 9장에서 모두 설명할 것이다. 위의 프로젝트가 비교적 최소의 pom.xml을 가지고 있지만, 프로젝트의 POM의 내용은 모든 부모 POM들, 사용자 세팅, 모든 사용 중인 프로필들에 대한 내용을 포함한다. 이러한 “effective” POM을 보려면, 간단한 프로젝트의 기본 디렉토리에서 다음의 명령을 실행한다.

$ mvn help:effective-pom

위의 내용을 실행했을 때 메이븐의 기본 세팅으로 노출된 더 많은 POM을 보게된다. 이 goal은 빌드를 디버그하려고 하고 어떻게 모든 현재 프로젝트의 상위 POM들이 효율적인 POM에 작용하는지를 보기를 원할 때에 작업할 수 있다. Maven Help 플러그인에 대한 더 많은 정보는 2장의 2.7절을 참고하기 바란다.

3.5 핵심 개념

이제 처음으로 메이븐을 실행보았으며, 이것이 메이븐의 몇가지 핵심 개념을 소개하는 좋은 지점이다. 예제 3-1에서 POM과 메이븐의 표준 디렉토리 구조에 모아진 몇가지 코드로 구성된 프로젝트를 생성했다. 그 다음에 여러 메이븐 플러그인 goal을 실행하기 위해 메이븐을 실행하는 인자로 생명주기 단계와 같이 메이븐을 실행했다. 마지막으로 로컬 레파지토리에 메이븐 산출물을 설치했다. 잠시 – "생명주기"가 무엇인가? "로컬 레파지토리"가 무엇인가? 다음 절은 메이븐의 주요 개념 몇가지를 정의한다.

3.5.1 Maven 플러그인과 goal

이전 절에서 명령행 인자의 두가지 서로 다른 유형으로 메이븐을 실행했다. 첫번째 명령은 단일 플러그인 goal로 Archetype 플러그인의 create goal이었다. 메이븐의 두번째 실행은 생명주기 단계인 install 이었다. 단일 메이븐 플러그인 goal을 실행하려면, mvn archetype:create 표현을 사용했으며, 여기서 archetype은 플러그인의 식별자이며 create는 goal의 식별자이다. 메이븐이 플러그인 goal을 실행할 때 표준 출력에 다음과 같이 플러그인 식별자와 goal 식별자를 보여준다.

$ mvn archetype:create -DgroupId=org.sonatype.mavenbook.ch03 \
                                        -DartifactId=simple \
                                        -DpackageName=org.sonatype.mavenbook
...
[INFO] [archetype:create]
[INFO] artifact org.apache.maven.archetypes:maven-archetype-quickstart: \
       checking for updates from central
...

메이븐 플러그인은 하나 이상의 goal의 집합체이다. (그림 3-1 참고) 메이븐 플러그인의 예는 JAR 파일 생성에 대한 goal들을 포함하는 Jar 플러그인, 소스 코드와 단위 테스트 컴파일을 위한 goal을 포함하는 Compiler 플러그인, 혹은 단위 테스트 실행과 보고서 생성을 위한 goal을 포함하는 Surefire 플러그인과 같은 간단한 핵심 플러그인을 들 수 있다. 다른 것으로 좀 더 특수한 메이븐 플러그인들은 인기있는 저장 라이브러리인 Hibernate와 통합을 위한 Hibernate3 플러그인과, 메이븐 빌드의 부분으로 Ruby를 실행하거나 Ruby로 메이븐 플러그인을 작성할 수 있는 JRuby 플러그인을 포함한다. 메이븐은 또한 사용자가 변경하는 플러그인을 정의하는 기능을 제공한다. 사용자 정의 플러그인은 Java, Ant, Groovy, BeanShell, 전에 언급했던 Ruby등을 포함하는 어떠한 언어로든지 작성될 수 있다.

그림 3-1. 하나의 플러그인은 goal들을 포함한다.


goal은 단독 goal로 혹은 더 큰 빌드의 부분으로 다른 goal들과 같이 실행될 수 있는 특정 작업이다. goal은 메이븐에서 "단위 작업"이다. goal의 예들은 프로젝트에 대한 모든 소스 코드를 컴파일하는 Compiler 플러그인의 compile goal, 혹은 단위 테스트를 실행할 수 있는 Surefire 플러그인의 test goal을 들 수 있다. goal은 기능을 변경하는데 사용될 수 있는 설정 속성으로 설정된다. 예를 들어, Compiler 플러그인의 compile goal은 대상 JDK 버전을 지정하거나 컴파일러 최적화 사용을 할지에 대한 여부를 가능하게 하는 여러 설정 파라미터를 정의한다. 이전 예제에서 명령행 파라미터인 -DgroupId=org.sonatype.mavenbook.ch03 과 -DartifactId=simple를 통해 Archetype 플러그인의 create goal에 설정 파라미터로 groupId와 artifactId를 넘겨주었다. 또한 create goal에 packageName 파라미터로 org.sonatype.mavenbook 를 넘겨주었다. 만일 packageName 파라미터를 생략했다면, 패키지 명은 org.sonatype.mavenbook.ch03 로 기본값이 세팅되었을 것이다.

유의
플러그인 goal을 참조할 때 pluginId:goalId 과 같이 짧은 표기를 종종 사용한다. 예를 들어, Archetype 플러그인의 create goal을 참조할 때 archetype:create 라고 작성한다.

goal은 의미있는 기본값을 정의할 수 있는 파라미터를 정의한다. archetype:create 예제에서 goal이 명령행에서 생성되는 데에 어떤 종류의 archetype 이었는지 지정하지 않았다. 단순히 groupId와 artifactId에 넘겼다. 이는 설정보다 규약 우선에 대한 첫번째 예제이다. create goal 에 대해 규약은, 즉 기본값은 Quickstart라고 하는 단순한 프로젝트를 생성하는 것이다. create goal 은 maven-archetype-quickstart 의 기본값을 가지는 archetypeArtifactId 속성값을 정의한다. Quickstart archetype은 POM과 단일 클래스를 포함하는 최소 프로젝트 셀을 생성한다. Archetype 플러그인은 이 첫번째 예제에서 말하는 것보다 훨씬 더 강력하지만, 새로운 프로젝트를 빠르게 시작하는 것을 얻는 좋은 방법이다. 이 책 이후에 어떻게 Archetype 플러그인이 웹 어플리케이션과 같은 좀 더 복잡한 프로젝트를 생성하는데 사용될 수 있는지와, 자신의 프로젝트를 정의하는데 Archetype 플러그인을 어떻게 사용하는지를 보여줄 것이다.
메이븐의 핵심은 프로젝트의 빌드에 관련된 특정 작업과 관계가 거의 없다. 그 자체로, 메이븐은 코드를 어떻게 컴파일 하는지 혹은 JAR 파일을 어떻게 만드는지조차 알지 못한다. 모든 이러한 작업을 편재 필요에 의해서 다운로드 받고 중앙 메이븐 레파지토리로부터 정기적으로 업데이트받는 Compiler 플러그인과 Jar 플러그인과 같은 메이븐 플러그인에게 위임한다. 메이븐을 다운로드 받을 때 메이븐의 핵심을 받게되는데, 이는 명령행을 어떻게 파싱하고, 클래스패스를 어떻게 관리하고, POM 파일을 어떻게 파싱하고, 필요에 의해서 메이븐 플러그인을 어떻게 다운로드 받는지만을 아는 매우 기초적인 셀로 구성된다. 메이븐의 핵심으로부터 Compiler 플러그인을 분리시키고 업데이트 기능에 대해 제공함으로써 메이븐은 사용자가 컴파일러에서 가장 최근의 선택을 접근하게 하는 것을 쉽게 만든다. 이러한 방법으로 메이븐 플러그인은 공통 빌드 로직의 보편적인 재사용을 가능하게 한다. 빌드 파일에 컴파일 작업을 정의하지 않는다. 메이븐의 모든 사용자가 공유하는 Compiler 플러그인을 사용한다. 만일 Compiler 플러그인에 대해 향상이 존재하면, 메이븐을 사용하는 모든 프로젝트는 이러한 변경으로 인해 즉시 이익을 얻는다. (그리고, 만일 해당 Compiler 플러그인을 좋아하지 않는다면, 자신만의 구현으로 이를 대체할 수 있다.)

3.5.3. 메이븐 생명주기

이전 절에서 실행했던 두번째 명령은 mvn install 이었다. 이 명령은 플러그인 goal을 지정하지 않았다. 대신에 메이븐 생명주기 단계를 지정했다. 단계는 메이븐이 소위 "빌드 생명주기"라고 하는 하나의 단계(step)이다. 빌드 생명주기는 프로젝트를 빌드하는데 관련된 순차적인 단계들이다. 메이븐은 수많은 생명주기를 지원하지만, 가장 자주 사용되는 것은 기본 메이븐 생명주기로, 프로젝트의 기본적인 통합을 검증하는 단계로 시작해서 제품으로 프로젝트를 배포하는 것과 관련된 단계로 끝난다. 생명주기 단계들은 검증(validating), 테스팅, 혹은 배포와 같이 단지 정의되고, 의도적으로 모호하며, 서로 다른 프로젝트에서 서로 다른 것을 의미할 수도 있다. 예를 들어, JAR를 만드는 프로젝트에서 패키지 단계는 “이 프로젝트를 JAR로 만들어라”라는 의미이다. 웹 어플리케이션을 만드는 프로젝트에서, 패키지 단계는 WAR 파일을 만들 수도 있다. 그림 3-2는 기본 메이븐 생명주기에 대한 단순화한 표현을 보여주고 있다.

그림 3-2. 생명주기는 일련의 단계이다.


플러그인 goal은 생명주기 단계에 붙을 수 있다. 메이븐이 생명주기의 단계를 통해 이동할 때, 각각의 특정 단계에 붙는 goal을 실행할 것이다. 각각의 단계는 그 안에 0개 이상의 goal을 가질 수 있다. 이전 절에서, mvn install 을 실행했을 때 하나 이상의 goal이 실행되는 것을 볼 수 있었을 것이다. mvn install 을 실행한 후에 결과를 살펴보면, 다양한 goal들이 실행되는 것을 볼 수 있다. 이 단순한 예제가 패키지 단계에 도달했을 때, Jar 플러그인의 jar goal을 실행했다. 단순한 Quickstart 프로젝트가 (기본적으로) jar 패키징 타입을 가지기 때문에 jar:jar goal은 패키지 단계에 포함된다. (그림 3-3 참고)

그림 3-3. 하나의 goal은 하나의 phase에 묶인다.


패키지 단계는 jar 패키징과 같이 프로젝트에 대한 JAR 파일을 생성하는 것을 알고 있다. 하지만 그 이전 goal인 compiler:compile과 surefire:test와 같은 것은 무엇인가? 이러한 goal들은 메이븐 생명주기의 패키징 이전 단계를 거치는 Maven 단계로 실행된다. 단계를 실행하는 것은 명령행에 지정된 단계로 끝나는 순차적으로 모든 수행되는 단계를 먼저 실행하는 것이다. 각 단계는 0개 이상의 goal에 대응되며, 어떤 플러그인 설정이나 변경을 수행하지 않았기 때문에 이 예제는 기본 생명주기에 표준 플러그인 goal들을 엮는다. 다음 goal들은 메이븐이 package로 끝나는 기본 생명주기를 수행할 때에 순차적으로 실행되는 것들이다.

resources:resources
Resources 플러그인의 resources goal은 resources 단계의 것이다. 이 goal은 src/main/resources 와 다른 설정된 리소스 디렉토리로부터 모든 리소스들을 결과 디렉토리로 복사한다.

compiler:compile
Compiler 플러그인의 compile 플러그인은 compile 단계의 것이다. 이 goal은 src/main/java 와 다른 설정된 소스 디렉토리에서 결과 디렉토리로 모든 소스 코드를 컴파일한다.

compiler:testCompile
Resources 플러그인의 testResources goal은 test-resources 단계의 것이다. 이 goal은 src/test/resources 와 다른 설정된 테스트 리소스 디렉토리에서 모든 리소스들을 테스트 결과 디렉토리로 복사한다.

surefire:test
Surefire 플러그인의 test goal은 test 단계의 것이다. 이 goal은 모든 테스트들을 실행하고 세부적인 결과를 담는 결과물을 만들어낸다. 기본적으로 이 goal은 테스트 실패가 있는 경우 빌드를 중단한다.

jar:jar
Jar 플러그인의 jar goal은 package 단계의 것이다. 이 goal은 결과 디렉토리를 JAR 파일로 패키징한다.

그림 3-4. 묶여진 goal들은 이들의 단계가 실행시 수행된다.


요약하면 mvn install 실행할 때 메이븐은 설치까지 모든 단계들을 실행하고, 생명주기 단계들을 거치는 과정에서 각 단계에 속한 모든 goal들을 실행한다. (그림 3-4 참고) 메이븐 생명주기 goal을 실행하는 대신에 다음과 같이 일련의 플러그인 goal들을 지정함으로써 동일한 결과를 얻을 수 있다.

mvn resources:resources \
    compiler:compile \
    resources:testResources \
    compiler:testCompile \
    surefire:test \
    jar:jar

package 단계를 실행하는 것은 특정 빌드에 관련된 모든 goal들을 추적하는데 선호된다. 또한 메이븐을 사용하는 모든 프로젝트가 잘 정의된 표준들을 고착화시키는 것을 가능하게 해준다. 생명주기는 개발자가 각각의 특정 프로젝트의 빌드에 대한 세부사항을 잘 알 필요도 없이 하나의 메이븐 프로젝트에서 다른 것으로 이동하는 것을 가능하게 해준다.

3.5.3. Maven 위상(Coordinate)

Archetype 플러그인은 pom.xml이라는 파일과 같이 프로젝트를 생성했다. 이것은 프로젝트에 대한 선언적인 기술인 프로젝트 객체 모델(Project Object Model, POM)이다. 메이븐은 goal을 실행할 때 각각의 goal은 프로젝트의 POM에 정의된 정보를 접근한다. jar:jar goal 은 JAR 파일을 생성할 필요가 있으며, JAR 파일의 이름이 무엇인지를 찾기 위해 POM을 참조한다. compiler:compile 이 자바 소스 코드를 바이트코드로 컴파일할 때, compile goal에 대한 파라미터가 있는지를 살펴보기 위해 POM을 참조한다. goal들이 POM 영역에서 실행한다. goal은 수행하려는 프로젝트에 대한 행위이며, 프로젝트는 POM에 의해 정의된다. POM은 프로젝트의 이름을 지정하며, 프로젝트에 대한 유일한 식별자 (위상)를 제공하며, 의존성, 부모, 선수 필수를 통해 해당 프로젝트와 다른 프로젝트 간의 관계를 정의한다. POM은 플러그인의 행위를 변경하고 프로젝트와 관련된 커뮤니티와 개발자들에 대한 정보를 제공한다.
메이븐 위상은 프로젝트, 의존관계, 혹은 메이븐 POM의 플러그인을 유일하게 식별하는데 사용될 수 있는 몇가지 식별들을 정의한다. 그림 3-5에 나타난 POM을 살펴보자.

그림 3-5. 메이븐 프로젝트의 위상


이 프로젝트의 메이븐 위상인 groupId, artifactId, version, packaging을 표시했다. 이러한 복합적인 식별자들은 프로젝트의 위상을 구성한다. 다른 위상 체계와 같이 Maven 위상은 일반적인 것에서 특수한 것까지 “공간”의 특정 지점에 대한 주소이다. 메이븐은 하나의 프로젝트가 의존관계, 플러그인, 상위 프로젝트 참조 중의 한가지 형태로 다른 프로젝트와 관련될 때, 이 위상을 통해 프로젝트를 찾는다. 메이븐 위상은 groupId:artifactId:packaging:version 과 같은 형태로 구분자로 콜론을 사용해서 종종 작성된다. 현재 프로젝트에 대한 pom.xml 파일에서 위상은 mavenbook:my-app:jar:1.0-SNAPSHOT 처럼 표현된다. 이러한 표기법은 또한 프로젝트 의존관계에도 적용된다. 위의 프로젝트는 JUnit 버전 3.8.1을 필요로 하며, junit:junit:jar:3.8.1에 대한 의존관계를 포함한다. 위상의 각 부분에 대한 좀 더 많은 정보가 다음과 같다.

groupId
그룹, 기업, 팀, 조직, 프로젝트, 혹은 다른 그룹. 단체 식별자에 대한 규약은 프로젝트를 만드는 조직의 도메인 명을 반대로 해서 시작한다. Sonatype의 프로젝트들은 com.sonatype을 시작하는 groupId를 가질 것이며, Apache 소프트웨어 재단의 프로젝트들은 org.apache로 시작하는 groupId를 가지게 된다.

artifactId
단일 프로젝트를 표현하는 groupId 하의 유일한 식별자

version
프로젝트의 특정 배포판. 배포된 프로젝트는 프로젝트의 특정 버전을 참조하는 고정된 버전 식별자를 가진다. 현재 개발 중인 프로젝트는 SNAPSHOT으로 버전을 표시하는 특별한 식별자를 사용할 수 있다.

프로젝트의 패키징 형태도 메이븐의 위상의 중요한 요소이지만, 프로젝트의 유일한 식별자의 부분은 아니다. 프로젝트의 groupId:artifactId:version 는 프로젝트를 유일하게 만든다. 동일한 세개의 groupId, artifactId, version 식별자가 있는 프로젝트를 가질 수 없다.

packaging
프로젝트에 의해 생성되는 패키징되는 결과를 표현하는 기본적으로 jar인 프로젝트의 유형. jar를 패키징하는 프로젝트는 JAR 파일을 생성하며, war 를 패키징하는 프로젝트는 웹 어플리케이션을 만든다.

위의 네가지 요소들은 다른 "메이븐으로 만든" 프로젝트들의 거대한 공간에 하나의 특정 프로젝트를 위치시키고 사용하는 열쇠가 된다. (그림 3-6 참조) 메이븐 레파지토리 (공공, 사적, 로컬)는 이러한 식별자들을 따라서 구성된다. 이 프로젝트가 로컬 메이븐 레파지토리로 설치될 때, 사용하기 원하는 다른 프로젝트에 즉시 로컬에서 사용 가능하게 된다. 해야하는 것은 특정 산출물에 대한 유일한 메이븐 위상을 사용하는 다른 프로젝트에 대한 의존관계로 추가하는 것이다.

그림 3-6. 메이븐 공간은 프로젝트의 위상 시스템이다.



3.5.4. 메이븐 레파지토리

처음에 메이븐을 실행할 때 원격 Maven 레파지토로부터 수많은 파일들을 다운로드받는 것을 볼 수 있을 것이다. 만일 이장에서 설명한 Simple 프로젝트가 메이븐을 실행하는 첫번째라면, 처음 할 것은 resources:resource goal에 의해서 실행될 때 Resources 플러그인의 가장 최근 배포판을 다운받는 것이다. 메이븐에서 산출물과 플러그인은 필요시에 원격 레파지토리로부터 얻어온다. 초기 메이븐 다운로드(1.5 MiB)가 그렇게 작은 이유 중에 하나는 메이븐은 플러그인의 방식으로 많은 것을 탑재하지 않는다. 메이븐은 최소 만을 탑재하며 필요할 때 원격 레파지토리로부터 가지고 온다. 메이븐은 기본 원격 레파지토리 위치(http://repo1.maven.org/maven2)를 포함하고 있는데, 핵심 메이븐 플러그인과 의존관계를 다운로드할 때 사용한다.
종종 무료가 아니거나 공식으로 배포되는 것이 아닌 라이브러리에 대한 의존을 갖는 프로젝트를 작성하는 경우가 있다. 그러한 경우, 조직 네트워크 내에 별도의 레파지토리를 세팅하거나 수동으로 다운로드와 설치를 할 필요가 있다. 기본 원격 레파지토리는 조직에서 관리하는 별도 메이븐 레파지토리를 참조하게끔 변경하거나 추가할 수 있다. 다중 프로젝트는 조직이 공식 메이븐 레파지토리에 대한 미러를 관리하고 유지하도록 할 수 있다.
메이븐 레파지토리를 만드는 것은 무엇인가? 구조에 의해서 정의된다. 하나의 레파지토리는 구조에 저장된 프로젝트 산출물들과 메이븐에 의해 쉽게 이해될 수 있는 형식에 대한 집합체이다. 모든 것은 프로젝트의 위상에 밀접하게 일치하는 디렉토리 구조에 저장된다. 이러한 구조는 웹 브라우저를 열고  http://repo1.maven.org/maven2/ 의 중앙 메이븐 레파지토리로 이동해서 볼 수 있다. org.apache.commons:commons-email:1.1를 가지는 산출물은 /org/apache/commons/commons-email/1.1/ 디렉토리 밑에 commons-email-1.1.jar라는 파일이 사용가능함을 볼 수 있을 것이다. Maven 레파지토리에 대한 표준은 다음과 같이 레파지토리의 루트에 상대적인 디렉토리에 산출물을 저장하는 것이다.

/<groupId>/<artifactId>/<version>/<artifactId>-<version>.<packaging>

메이븐은 원격 레파지토리에서 로컬 PC에 산출물들과 플러그인들을 다운로드받고 이러한 산출물들을 로컬 메이븐 레파지토리에 저장한다. 일단 메이븐이 원격 레파지토리로부터 산출물을 다운받았으면, 메이븐이 다른 곳에서 찾기 전에 로컬 레파지토리에서 해당 산출물을 항상 찾기 때문에 다시 해당 산출물을 다운받을 필요가 전혀 없다. 윈도우 XP에서 로컬 레파지토리는 C:\Documents and Settings\USERNAME\.m2\repository 와 같으며, 윈도우 비스타에서 로컬 레파지토리는 C:\Users\USERNAME\.m2\repository 에 있다. 유닉스 시스템에서 로컬 메이븐 레파지토리는 ~/.m2/repository 에 있다. 이전 절에서 생성했던 단순한 프로젝트와 같은 프로젝트를 빌드할 때 install 단계는 로컬 메이븐 레파지토리에 프로젝트의 산출물들을 설치하는 goal을 실행한다.
로컬 레파지토리에서 간단한 프로젝트에서 생성된 산출물을 볼 수 있어야만 한다. 만일 mvn install 명령을 실행한다면 메이븐은 로컬 레파지토리에 프로젝트의 산출물을 설치한다.

$ mvn install
...
[INFO] [install:install]
[INFO] Installing .../simple-1.0-SNAPSHOT.jar to \
       ~/.m2/repository/org/sonatype/mavenbook/simple/1.0-SNAPSHOT/ \
       simple-1.0-SNAPSHOT.jar
...

위의 명령의 결과에서 볼 수 있듯이 메이븐은 프로젝트의 JAR 파일을 로컬 레파지토리에 설치했다. 메이븐은 로컬 프로젝트끼리 의존관계를 공유하기 위해 로컬 레파지토리를 사용한다. 만일 project-a와 project-b 라는 두개의 프로젝트를 개발하고 project-b가 project-a에 의해 만들어진 산출물을 의존하고 있다면 메이븐은 project-b를 빌드할 때 로컬 레파지토리로부터 project-a의 산출물들을 가지고 온다. 메이븐 레파지토리는 원격 레파지토리로부터 다운로드받은 산출물들에 대한 로컬 캐시와 프로젝트가 다른 프로젝트에 의존관계를 갖게하는 기능을 수행한다.

3.5.5. 메이븐의 의존성 관리

이 장의 단순한 예제 프로젝트에서 메이븐은 JUnit 의존성인 junit:junit:3.8.1 에 대한 위상을 메이븐 레파지토리의 경로 /junit/junit/3.8.1/junit-3.8.1.jar로 연결했다. 메이븐 위상에 근거한 레파지토리의 산출물을 위치시키는 기능은 프로젝트의 POM에 의존성을 정의하는 기능을 제공한다. 단순한 프로젝트의 pom.xml 파일을 살펴보면, <dependencies>로 처리된 부분을 있고, 이 부분이 단일 dependency인 JUnit을 포함하고 있다는 것일 볼 수 있다.
더 복잡한 프로젝트는 하나 이상의 의존성을 포함하거나, 다른 산출물에 대한 의존관계를 갖는 의존성들을 포함할 수도 있다. 연결(transitive) 의존성에 대한 지원은 메이븐의 가장 강력한 기능 중에 하나이다. 한 프로젝트가 5내지 10개의 다른 라이브러리들(예를 들어, Spring이나 Hibernate)을 차례로 의존하고 있는 어느 한 라이브러리에 대해 의존관계가 잇다고 가정하자. 이러한 모든 의존관계들을 계속해서 추적하고 명시적으로 pom.xml 에 이들을 나열해야 하는 대신에 단순히 사용하는 라이브러리에 대한 의존만을 맺으면 메이븐은 내부적으로 프로젝트의 의존관계에 이 라이브러리의 의존관계들을 추가한다. 메이븐은 또한 의존관계 간의 충돌이 작용하는 것을 살펴보고 기본 행위에 대한 변경하고 특정 연결 의존관계를 배제하도록 하는 기능을 제공한다.
이전 예제를 실행했을 때 로컬 레파지토리에 다운로드받았던 의존관계를 살펴보자. ~/.m2/repository/junit/junit/3.8.1/ 밑에 있는 로컬 레파지토리 경로를 보자. 이 장의 예제를 따라서 해보았다면 메이븐이 다운로드받은 산출물에 대한 권한을 검증하는데 사용하는 몇가지 체크섬 파일과 함께 junit-3.8.1.jar라는 파일과 junit-3.8.1.pom 파일이 있을 것이다. 메이븐은 JUnit JAR 파일만을 다운로드 받지 않고, JUnit 의존관계에 대한 POM 파일도 다운로드 받는다는 것에 유의하라. 메이븐이 산출물과 더불어서 POM 파일을 다운로드 받는다는 사실은 연결(transitive) 의존관계에 대한 메이븐의 지원에 핵심이다.
로컬 레파지토리에 프로젝트의 산출물을 설치할 때 메이븐이 동일한 디렉토리에 JAR 파일로 프로젝트의 pom.xml 파일의 변경된 버전이 배포됨을 역시 볼 수 있을 것이다. 레파지토리에 POM 파일을 저장하는 것은 다른 프로젝트들에게 해당 프로젝트에 대한 정보, 즉 가장 중요한 어떠한 의존관계를 가지고 있는지에 대한 정보를 제공한다. Project B가 Project A에 대해 의존관계가 있다면, Project A의 의존관계들 역시 의존관계가 있다. 메이븐이 여러 메이븐 위상들로부터 의존 산출물을 연겨할 때 POM 역시 가지고 오고 의존성 POM에게 연결 의존성들을 찾는 것을 의뢰한다. 이러한 연결 의존성들은 그 다음에 현재 프로젝트의 의존성으로 추가된다.
메이븐의 의존성은 JAR 파일만이 아니다. 다른 산출물들에 대한 의존관계를 차례로 선언할 수 있는 POM 파일도 포함된다. 이러한 의존성에 대한 의존관계는 연결 의존성이라고 하며, 메이븐 레파지토리가 단지 바이트 코드 그 이상을 저장한다는 사실에 의해 가능해졌다. 즉 산출물에 대한 메타데이터를 저장한다. 그림 3-7은 연결 의존성에 대한 가능한 시나리오를 보여준다.

그림 3-7. 메이븐은 연결 의존성들을 해결한다.


위 그림에서 project-a는 project-b와 project-c에 대해 의존관계를 가지며, project-b는 project-d에 대해 의존관계를 가지며, project-c는 project-e에 대해 의존관계를 가진다. project-a에 대해 전체적인 방향성이 있고 연결 의존관계는 project-b, project-c, project-d, project-e가 될 것이지만, project-a는 project-b와 project-c에 대한 의존관계만을 정의하면 된다. 연결 의존관계는 프로젝트가 몇가지 작은 의존관계(Hibernate, Apache Struts, 혹은 Spring 프레임워크와 같은)를 가지는 다른 프로젝트에 대해 의존관계를 가질 때에 사용된다. 메이븐 은 또한 프로젝트의 클래스패스로부터 연결 의존관계를 제외할 수 있는 기능을 제공한다.
메이븐은 또한 서로 다른 의존관계 영역에 대해서도 제공한다. 간단한 프로젝트의 pom.xml은 단일 의존관계인 junit:junit:jar:3.8.1 를 테스트 영역으로 포함한다. 의존관계가 테스트 영역을 가질 때에 Compiler 플러그인의 compile goal에서 사용되지 않는다. compiler:testCompile와 surefire:test goal에서만 클래스패스로 추가될 것이다.
프로젝트에 대한 JAR 파일을 생성할 때 의존관계들은 생성되는 산출물에 같이 포함되지 않는다. 컴파일할 때에만 사용된다. WAR나 EAR 파일을 생성하는데 메이븐을 사용한다면 생성된 산출물에 의존관계를 포함하도록 메이븐을 설정할 수 있으며, provided 영역을 사용해서 WAR 파일로부터 특정 의존관계들을 제외하도록 설정할 수도 있다. Provided 영역은 메이븐에게 해당 의존관계가 컴파일 시에만 필요하고, 빌드의 결과에 포함되지 않아야 된다는 것을 지시한다. 이 영역은 웹 어플리케이션을 개발할 때 사용된다. 서블릿 명세에 대해 코드를 컴파일할 필요가 있지만, 웹 어플리케이션의 WEB-INF/lib 디렉토리에 서블릿 API JAR를 포함하기를 원하지 않는다.

3.5.6. 사이트 생성과 레포팅

메이븐의 또 다른 중요 기능은 문서와 리포트를 생성하는 기능이다. 간단한 프로젝트의 디렉토리에서 다음의 명령을 실행하라.

$ mvn site

이는 site 생명주기 단계를 실행한다. 코드 생성, 리소스 처리, 컴파일, 패키징 등을 담당하는 기본 빌드 생명주기와 달리 이 생명주기는 src/site 디렉토리에 사이트 내용을 처리하고 리포트를 생성하는 데에만 관여한다. 이 명령을 실행한 후에 target/site 디렉토리에 프로젝트 웹 사이트를 볼 수 있을 것이다. target/site/index.html 를 보면 프로젝트 사이트의 기본적인 셀을 볼 수 있다. 이 셀은 왼쪽 메뉴에 "Project Reports" 밑에 몇가지 리포트를 포함하고, "Project Information" 밑에 프로젝트, 의존관계, 관련 개발자들에 대한 정보를 포함하고 있다. 간단한 프로젝트의 웹 사이트는 대부분이 비어있는데, 이는 POM이 위상, 이름, URL, 단일 테스트 의존관계를 제외하고 그 자체에 대한 정보가 거의 없기 때문이다.
이 사이트에서 어떤 기본적인 리포트들이 사용가능한지를 볼 수 잇다. 단위 테스트 리포트는 프로젝트의 모든 단위 테스트에 대한 성공과 실패를 보여준다. 또 다른 리포트는 프로젝트의 API에 대한 Javadoc을 생성한다. 메이븐은 단위 테스트 커버리지를 조사하하는 Clover 리포트, 코드 검토에 사용되는 HTML 소스 코드 목록을 생성하는 JXR 리포트, 다양한 코딩 문제에 대한 소스 코드를 분석하는 PMD 리포트, 코드기반의 패키지 간의 의존관계를 분석하는 JDepend와 같은 모든 분야에서 설정이 가능한 리포트를 제공한다. pom.xml 파일을 통해 빌드에 포함되는 리포트를 설정함으로써 사이트 리포트를 변경할 수 있다.

3.6 요약

이 장에서 단순한 프로젝트를 만들었고, JAR 파일로 프로젝트를 패키징했고, 다른 프로젝트에 의한 사용을 위해 메이븐 레파지토리에 JAR를 설치했고, 문서로 사이트를 생성했다. 이러한 것을 코드에 한줄을 작성하거나 한줄의 설정파일을 작성하지 않고도 수행했다. 메이븐의 핵심 개념 몇가지에 대한 정의를 만드는데 시간을 할해하기도 했다. 다음 장에서 의존관계를 추가하고 단위 테스트를 설정하기 위해서 프로젝트 pom.xml 파일을 변경하고 수정하는 것으로 시작할 것이다.

<<Pre  Next>>

Creative Commons License
Elvis Lee에 의해 창작된 메이븐 가이드 은(는) 크리에이티브 커먼즈 저작자표시-비영리-동일조건변경허락 2.0 대한민국 라이선스에 따라 이용할 수 있습니다.
www.sonatype.com의 저작물에 기초

반응형