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

6장. 다중 프로젝트

by javauser 2008. 10. 30.
6.1. 소개

이 장에서 이전 두개의 장의 예를 조합하여 다중 모듈 프로젝트를 만들 것이다. 4장에서 만든 simple-weather 코드는 웹 페이지의 날씨 예측 정보를 조회하고 보여주는 웹 어플리케이션을 만들기 위해 5장에서 정의한 simple-webapp 프로젝트와 같이 합쳐질 것이다. 이 장의 끝에서 복잡하고 다중 모듈 프로젝트를 개발하기 위해 메이븐을 사용할 수 있을 것이다.

6.1.1. 이장의 예제 다운로딩

이 예제에서 만들어진 다중 모듈 프로젝트는 4장과 5장에서 만들어진 프로젝트의 수정된 버전으로 구성되며, 이 다중 모듈 프로젝트를 생성하기 위해서 Maven Archetype 플러그인을 사용하지 않는다. 이 장의 내용을 읽는 동안 보충 참고로 사용하기 위해 예제 코드의 복사본을 다운로드 받는 것을 강력하게 권한다. 이 장의 예제 프로젝트는 http://www.sonatype.com/book/mvn-examples-1.0.ziphttp://www.sonatype.com/book/mvn-examples-1.0.tar.gz에서 책의 예제 코드와 같이 다운로드 받을 수 있다. 디렉토리에서 압축을 풀고, ch06/ 디렉토리로 이동하라. 거기서 이 장에서 개발한 다중 모듈 메이븐 프로젝트를 포함하는 simple-parent/ 라는 디렉토리를 볼 수 있을 것이다. 이 디렉토리에서 pom.xml 과 두개의 하위 모듈 디렉토리인 simple-weather/ 와simple-webapp/ 를 볼 수 있을 것이다. 웹 브라우저를 통해서 예제를 보기 원한다면, http://www.sonatype.com/book/examples-1.0 을 방문하여 ch06/ 디렉토리를 클릭하라.

6.2. Simple 부모 프로젝트

다중 모듈 프로젝트는 하나 이상의 하위 모듈을 참고하는 부모 POM에 의해서 정의된다. simple-parent/ 디렉토리에서 simple-parent/pom.xml 의 부모 POM (최상위 POM이라고도 한다)을 발견할 수 있을 것이다. 예제 6-1을 참고하라.

예제 6-1. simple-parent 프로젝트의 POM
<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.ch06</groupId>
  <artifactId>simple-parent</artifactId>
  <packaging>pom</packaging>
  <version>1.0</version>
  <name>Chapter 6 Simple Parent Project</name>
 
  <modules>
    <module>simple-weather</module>
    <module>simple-webapp</module>
  </modules>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <configuration>
            <source>1.5</source>
            <target>1.5</target>
          </configuration>
        </plugin>
      </plugins>
   </pluginManagement>
  </build>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

부모는 groupId가 com.sonatype.maven이고, artifactId가 simple-parent이고, version이 1.0인 메이븐 위상을 정의하고 있음을 유의하라. 부모 프로젝트는 이전 프로젝트처럼 JAR나 WAR를 생성하지 않는다. 대신에, 단지 다른 메이븐 프로젝트를 참조하는 POM이다. 단지 프로젝트 객체 모델을 제공하는 simple-parent과 같은 프로젝트에 대한 적절한 packaging은 pom이다. pom.xml 의 다음 부분은 프로젝트의 하위모듈들이 열거되어 있다. 이러한 모듈들은 <modules> 요소에 정의되며, 각각의 <modules> 요소는 simple-parent/ 디렉토리의 하위 디렉토리에 대응된다. 메이븐은 pom.xml 파일에 대한 이러한 디렉토리를 참조한다는 것과, 빌드에서 포함되는 메이븐 프로젝트의 목록에 하위 모듈을 추가하는 것을 알고 있다.

마지막으로 모든 하위 모듈에 의해 상속되는 몇가지 세팅을 정의한다. simple-parent 빌드 설정은 모든 자바 컴파일이 Java 5 JVM으로 대상을 설정한다. Compiler 플러그인은 기본적으로 생명주기 내에 있기 때문에, 이를 위해서 <pluginManagement> 부분을 사용할 수 있다. 이후 장에서 더 자세하게 <pluginManagement>를 다룰 것이지만, 기본 플러그인에 설정을 제공하는 것과 실질적인 플러그인 바인딩간의 분리는 이와 같은 방식으로 분리될 때 훨씬 보기가 쉽다. dependencies 요소는 전역 의존관계로 JUnit 3.8.1을 추가한다. 빌드 설정과 의존관계 모두가 모든 하위 모듈에 의해 상속받는다. POM 상속을 사용하는 것은 JUnit이나 Log4J와 같은 전체적인 의존관계에 대한 공통 의존관계를 추가하는 것을 가능하게 한다.

6.3. 간단한 날씨 모듈

제일 먼저 살펴볼 하위 모듈은 simple-weather 하위 모듈이다. 이 하위 모듈은 야후 날씨 피드와 상호작용하고 파싱하는 것을 담당하는 모든 클래스들을 포함한다. 예제 6-2를 참고하라.

예제 6-2. simple-weather 모듈의 POM
<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>
  <parent>
    <groupId>org.sonatype.mavenbook.ch06</groupId>
    <artifactId>simple-parent</artifactId>
    <version>1.0</version>
  </parent>
  <artifactId>simple-weather</artifactId>
  <packaging>jar</packaging>

  <name>Chapter 6 Simple Weather API</name>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-plugin</artifactId>
          <configuration>
            <testFailureIgnore>true</testFailureIgnore>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>

  <dependencies>
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.14</version>
    </dependency>
    <dependency>
      <groupId>dom4j</groupId>
      <artifactId>dom4j</artifactId>
      <version>1.6.1</version>
    </dependency>
    <dependency>
      <groupId>jaxen</groupId>
      <artifactId>jaxen</artifactId>
      <version>1.1.1</version>
    </dependency>
    <dependency>
      <groupId>velocity</groupId>
      <artifactId>velocity</artifactId>
      <version>1.5</version>
    </dependency>
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-io</artifactId>
      <version>1.3.2</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

simple-weather의 pom.xml 파일에서 위의 모듈이 Maven 위상을 사용해서 부모 POM을 참고하고 있음을 볼 수 있다. simple-weather에 대한 부모 POM은 org.sonatype.mavenbook의 groupId, simple-parent의 artifactId, 1.0의 version에 의해서 식별된다. 예제 6-3을 참고하라.

예제 6-3. WeatherService 클래스
package org.sonatype.mavenbook.weather;

import java.io.InputStream;

public class WeatherService {

    public WeatherService() {}

    public String retrieveForecast( String zip ) throws Exception {
        // Retrieve Data
        InputStream dataIn = new YahooRetriever().retrieve( zip );

        // Parse Data
        Weather weather = new YahooParser().parse( dataIn );

        // Format (Print) Data
        return new WeatherFormatter().format( weather );
    }
}

WeatherService 클래스는 src/main/java/org/sonatype/mavenbook/weather 에 정의되며, 4장에서 정외한 세개의 객체를 단순하게 호출한다. 이장의 예제에서 웹 어플리케이션 프로젝트에서 참조되는 서비스 객체를 포함하는 별도의 프로젝트를 생성할 것이다. 이는 엔터프라이즈 자바 개발에서 보편적인 모델이다. 종종 복잡한 어플리케이션은 단지 하나의 간단한 웹 어플리케이션 이상의 것을 구성한다. 여러 웹 어플리케이션들과 몇몇 명령행 어플리케이션으로 구성되는 엔터프라이즈 어플리케이션을 가질 수도 있다. 종종 수많은 프로젝트에서 재사용될 수 있는 서비스 클래스에 공통 로직을 뽑아내기를 원할 수도 있다. 이는 WeatherService 클래스를 만든 이유이다. 그렇게 함으로써 simple-webapp 프로젝트가 simple-weather에서 정의된 서비스 객체를 어떻게 참조하는지를 살펴볼 수 있다.
retrieveForecast() 메소드는 우편번호를 포함하는 String 을 받아들인다. 이 우편번호 파라미터는 그 다음에 YahooRetriever의 retrieve() 메소드로 넘겨지며, 이 메소드는 야후 날씨로부터 XML을 가지고 온다. YahooRetriever로부터 반환된 XML은 그 다음에 Weather 객체를 반환하는 YahooParser 의 parse() 메소드로 넘겨진다. 이 Weather 객체는 그 다음에 WeatherFormatter 에 의해 String 형태로 보여진다.

6.4. Simple 웹 어플리케이션 모듈

simple-webapp 모듈은 simple-parent 프로젝트에서 참조되는 두번째 하위 모듈이다. 이 웹 어플리케이션 프로젝트는 simple-weather 모듈에 대해 의존관계를 가지며, 야후 날씨 서비스 질의의 결과를 나타내는 몇가지 간단한 서블릿을 포함한다. 예제 6-4를 참고하라.

예제 6-4. simple-webapp 모듈의 POM
<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>
  <parent>
    <groupId>org.sonatype.mavenbook.ch06</groupId>
    <artifactId>simple-parent</artifactId>
    <version>1.0</version>
  </parent>

  <artifactId>simple-webapp</artifactId>
  <packaging>war</packaging>
  <name>simple-webapp Maven Webapp</name>
  <dependencies>
    <dependency>
      <groupId>org.apache.geronimo.specs</groupId>
      <artifactId>geronimo-servlet_2.4_spec</artifactId>
      <version>1.1.1</version>
    </dependency>
    <dependency>
      <groupId>org.sonatype.mavenbook.ch06</groupId>
      <artifactId>simple-weather</artifactId>
      <version>1.0</version>
    </dependency>
  </dependencies>
  <build>
    <finalName>simple-webapp</finalName>
    <plugins>
      <plugin>
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>maven-jetty-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

simple-weather 모듈은 HTTP 요청으로부터 우편번호를 읽어서, 예제 6-3에 나타난 WeatherService 를 호출하고, 응답의 Writer에게 결과를 출력하는 매우 간단한 서블릿을 정의한다. 예제 6-5를 참고하라.

예제 6-5. simple-webapp 의 WeatherServlet
package org.sonatype.mavenbook.web;

import org.sonatype.mavenbook.weather.WeatherService;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class WeatherServlet extends HttpServlet {
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
        throws ServletException, IOException {
        String zip = request.getParameter("zip" );
        WeatherService weatherService = new WeatherService();
        PrintWriter out = response.getWriter();
        try {
            out.println( weatherService.retrieveForecast( zip ) );
        } catch( Exception e ) {
            out.println( "Error Retrieving Forecast: " + e.getMessage() );
        }
        out.flush();
        out.close();
    }
}

WeatherServlet 에서 simple-weather에서 정의된 WeatherService 클래스의 인스턴스를 초기화시키니다. 요청 파라미터에서 제공된 우편번호는 retrieveForecast() 메소드로 넘겨지며, 결과 테스트는 응답의 Writer로 출력된다.
최종적으로 이러한 모든 것을 같이 엮는 것이 src/main/webapp/WEB-INF에 있는 simple-webapp에 대한 web.xml 이다. 그림 6-6에 보여지는 web.xml 의 <servlet> 과 <servlet-mapping>요소는 WeatherServlet에 /weather 요청 경로를 매핑한다.

예제 6-6. simple-webapp의 web.xml
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
  <servlet>
    <servlet-name>simple</servlet-name>
    <servlet-class>org.sonatype.mavenbook.web.SimpleServlet</servlet-class>
  </servlet>
  <servlet>
    <servlet-name>weather</servlet-name>
    <servlet-class>org.sonatype.mavenbook.web.WeatherServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>simple</servlet-name>
    <url-pattern>/simple</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>weather</servlet-name>
    <url-pattern>/weather</url-pattern>
  </servlet-mapping>
</web-app>


6.5 다중 모듈 프로젝트 빌드

야후 날씸 서비스와 상호작용하는 모든 일반적인 코드를 포함하는 simple-weather 프로젝트와 간단한 서블릿을 포함하는 simple-webapp 프로젝트를 사용해서 어플리케이션을 WAR 파일로 컴파일하고 패키지할 시간이다. 이렇게 하려면, 적설한 순서대로 양쪽의 프로젝트를 컴파일하고 설치해야 할 것이다. simple-webapp 는 simple-weather에 대한 의존관계를 가지고 있기 때문에 simple-parent 프로젝트로부터 다음과 같이 mvn clean install을 실행할 수 있을 것이다.

~/examples/ch06/simple-parent$ mvn clean install
[INFO] Scanning for projects...
[INFO] Reactor build order:
[INFO]   Simple Parent Project
[INFO]   simple-weather
[INFO]   simple-webapp Maven Webapp
[INFO] ----------------------------------------------------------------------------
[INFO] Building simple-weather
[INFO]    task-segment: [clean, install]
[INFO] ----------------------------------------------------------------------------
[...]
[INFO] [install:install]
[INFO] Installing simple-weather-1.0.jar to simple-weather-1.0.jar
[INFO] ----------------------------------------------------------------------------
[INFO] Building simple-webapp Maven Webapp
[INFO]    task-segment: [clean, install]
[INFO] ----------------------------------------------------------------------------
[...]
[INFO] [install:install]
[INFO] Installing simple-webapp.war to simple-webapp-1.0.war
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] ------------------------------------------------------------------------
[INFO] Simple Parent Project ................................. SUCCESS [3.041s]
[INFO] simple-weather ........................................ SUCCESS [4.802s]
[INFO] simple-webapp Maven Webapp ............................ SUCCESS [3.065s]
[INFO] ------------------------------------------------------------------------

메이븐이 하위 모듈을 가진 프로젝트에 대해서 실행될 때, 먼저 부모 POM을 로딩하고 모든 하위 모듈의 POM을 위치시킨다. 다음에 모든 이러한 프로젝트 POM을 Maven Reactor라고 하는 것에게 위치시킨다. Maven Reactor는 모듈간의 의존관계를 분석한다. Reactor는 상호 의존관계의 모듈이 적절한 순서대로 컴파일되고 설정되는 것을 보장하도록 컴포넌트 순서를 책임진다.

유의
Reactor는 변경할 필요가 없다면 POM에서 정의된 대로 모듈의 순서를 유지한다. 이에 대한 도움이 되는 개념 모델은 형제 프로젝트에 대한 의존관게를 갖는 모듈이 의존관계 순서에 만족할 때까지 목록의 "아래로 내려간다"라는 그림을 그리면 된다. 거의 드문 경우이지만, 예를 들어, 빌드 시작으로 거의 안정되지 않은 모듈을 사용하고자 원하는 경우, 빌드에 대한 모듈 순서를 재배열하는게 편할 수도 있다.

일단 Reactor가 어떤 프로젝트가 빌드되어야 하는지에 대한 순서를 결정하고 나면, 메이븐은 다중 모듈 빌드의 모든 모듈에 대해 지정된 goal을 실행한다. 이 에제에서 메이븐이 각 하위 모듈에 대해 효율적으로 mvn clean install을 실행하면서 simple-webapp 이전에 simple-weather를 빌드하는 것을 볼 수 있을 것이다.

유의
명령행에서 메이븐을 실행할 때 다른 생명주기 단계 이전에 clean 생명주기 단계를 지정하기를 종종 원할 것이다. clean을 지정했을 때 메이븐이 어플리케이션을 컴파일하고 패키징하기 전에 기존 결과를 지운다는 것을 이해해야 한다. clean을 실행하는 것은 필요하지 않지만, "clean 빌드"를 수행하고 있다는 확신을 위해 유용한 예방 조치이다.


6.6. 웹 어플리케이션 실행

다중 모듈 프로젝트가 상위 프로젝트인 simple-project 로부터 mvn clean install 로 설치되었다면, simple-webapp 프로젝트로 디렉토리를 변경하고 다음과 같이 Jetty 플러그인의 run goal을 실행한다.

~/examples/ch06/simple-parent/simple-webapp $ mvn jetty:run
[INFO] -------------------------------------------------------------------
[INFO] Building simple-webapp Maven Webapp
[INFO]    task-segment: [jetty:run]
[INFO] -------------------------------------------------------------------
[...]
[INFO] [jetty:run]
[INFO] Configuring Jetty for project: simple-webapp Maven Webapp
[...]
[INFO] Webapp directory = ~/examples/ch06/simple-parent/simple-webapp/src/
       main/webapp
[INFO] Starting jetty 6.1.6rc1 ...
2007-11-18 1:58:26.980::INFO:  jetty-6.1.6rc1
2007-11-18 1:58:26.125::INFO:  No Transaction manager found - if your webapp\
           requires one, please configure one.
2007-11-18 1:58:27.633::INFO:  Started SelectChannelConnector@0.0.0.0:8080
[INFO] Started Jetty Server

Jetty가 기동하면, 브라우저에서 http://localhost:8080/simple-webapp/weather?zip=01201 로 이동하면 날씨 결과를 볼 수 있을 것이다.



<<Pre                                                                                                                                                          Next>>
반응형