크리에이티브 커먼즈 라이선스
Creative Commons License

기존 Maven : The Definitive Guide에는 없던 내용이 온라인 상으로 추가되었다. 다음은 http://www.sonatype.com/books/maven-book/reference/flex-dev.html 에 있는 원문을 참조하여 번역한 내용이다.

19장. Flexmojo로 개발하기

19.1 개요
이 장은 Flex 애플리케이션과 라이브러리를 개발하기 위해 Maven을 사용하는데 사람들이 관심있는 Flexmojo 프로젝트에 대한 개요를 제공한다.

19.2 Flexmojo의 빌드 환경 설정
Maven으로 Flex 라이브러리와 애프리케이션을 컴파일하기 전에, 다음의 두가지 설정 작업이 필요하다.
  • Flex 프레임워크를 포함하는 레파지토리를 참조하도록 Maven Settings 설정
  • Flex 단위 테스팅을 지원하기 위해 PATH에 Flash Player 추가
  • (선택사항) Sonatype 플러그인 그룹을 포함시키기 위해 Maven Settings 설정

19.2.1. Flex 프레임워크를 가지는 레파지토리 참조
Flexmojo에 대한 Maven 환경을 셋업하려면, 두가지 선택사항이 있다. pom.xml에 Sonatype Flexmojos 레파지토리를 직접적으로 참조하거나, 혹은 Nexus 를 설치하고 레파지토리 관리자에 프록시 레파지토리로 Sonatype Flexmojos 레파지토리를 추가한다. 가장 직접적인 방법은 레파지토리에 직접적으로 참조하는 것으로, Nexus를 다운로드하고 설치하는 것은 자체 빌드가 만들어내는 산출물들을 캐싱하고 관리할 필요가 있는 통제와 유연성을 제공한다. 만일 Flexmojo를 직접 사용하기 원한다면 다음의 19.2.1.1. "POM에서 Sonatype의 Flexmojos 레파지티로 참조"를 읽으면 된다. 만일 개발 팀을 지원하도록 장기적인 관점에서의 해결책을 설정하려면, 19.2.1.2. "Nexus를 사용한 Sonatype의 Flexmojos 레파지토리 프록싱"을 계속해서 읽어라.

만일 조직이 프록시 원격 레파지토리로 Sonatype 의 Nexus를 이미 사용하고 있다면, 단일 Nexus 그룹을 지정하는 변경된 ~/.m2/settings.xml을 가지고 있을 것이다. 그러한 상황이라면, 개발팀이 참조하는 Nexus Repository 그룹에 Sonatype Flexmojos 레파지토리 그룹에 대한 프록시 레파지토리인 http://repository.sonatype.org/content/groups/flexgroup/ 를 추가해야 한다. 이 원격 그룹에 대한 프록시 레파지티로를 추가하고 그 다음에 Nexus 설치의 public repository group에 이 그룹을 추가하면 Nexus에 접근하는 사람은 Sonatype의 repository.sonatype.org Nexus 인스턴스의 산출물들에 대한 접근이 가능할 것이다.

19.2.1.1. POM에 Sonatype의 Flexmojos 레파지토리 참조
Flexmojos는 중앙 Maven 레파지티로에서 현재 서비스되지 않는 몇가지 산출물들을 의존한다. 이러한 산출물들은 Sonatype이 운영하는 레파지토리에서부터 사용이 가능하다. Flexmojos를 사용하려면, 프로젝트의 pom.xml에 이 레파지토리를 참조할 필요가 있다. 이와 같이 하려면, 프로젝트의 pom.xml에 예제 19.1 "POM에 Sonatype의 Flexmojos 레파지티로에 대한 참조 추가"에 나타난 것처럼 repositories 요소를 추가한다.

예제 19.1 POM에 Sonatype의 Flexmojos 레파지티로에 대한 참조 추가

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>test</groupId>
  <artifactId>test</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>pom</packaging>
 
  <modules>
    <module>swc</module>
    <module>swf</module>
    <module>war</module>
  </modules>

  <repositories>
    <repository>
      <id>flexmojos</id>
      <url>http://repository.sonatype.org/content/groups/flexgroup/</url>
    </repository>
  </repositories>

</project>


위의 예제에 있는 XML은 산출물과 플러그인들을 다운받으려고 할 때 Maven이 시도하는 레파지토리 목록의 해당 레파지토를 추가하는 것이다.

19.2.1.2. Nexus를 사용해서 Sonatype의 Flexmojos 레파지토리 프록싱
Sonatype 의 Flexmojos 레파지토를 직접적으로 참조하는 대신에 Sonatype은 레파지토리 관리자를 설치하고 Sonatype의 public 레파지토리를 프록시하는 것을 권한다. Nexus와 같은 레파지토리 관리자를 사용해서 원격 레파지토리를 프록싱할 때 빌드가 외부 자원에 직접적으로 의존하지 않도록 제어와 안정성을 가질 수 있다. 이러한 통제와 안정성 외에도 레파지토리 관리자는 빌드가 만들어내는 바이너리 산출물에 대한 배포 대상을 제공한다. Nexus에 대한 다운로드, 설치, 설정에 대한 내용은 Nexus를 사용한 레파지토리 관리자의 설치장을 참조하라. 일단 Nexus가 설치되고 기동했다면, Sonatype의 public 레파지토리에 대한 프록시 레파지토리를 추가하기 위해 다음의 단계를 수행한다.

새로운 프록시 레파지토리를 추가하려면, Nexus UI의 좌측에 있는 Nexus 메뉴에서 Views/Repositories 밑에 있는 Repositories를 클릭한다. Repositories를 클릭하면 Repositories 패널이 로딩된다. Repositories 패널에서 Add.. 버튼을 클릭하고 그림 19.1 "Sonatype Nexus에 프록시 레파지토리 추가"에 나타난 Proxy Repository를 선택한다.


그림 19.1 Sonatype Nexus에 프록시 레파지토리 추가

새로운 Proxy 레파지토리가 생성되었다면, Sonatype Flexmojos 레파지토리를 설정할 필요가 있다. 새로운 repository를 선택하고, 창 하단부에 있는 Configuration 탭을 선택한다. 그림 19.2 "Sonatype Flexmojos 프록시 레파지토리 설정"에 나타난 것처럼 해당 값으로 필드를 채운다.
  • Repository ID는 "sonatype-flexmojos"
  • Repository Name은 "Sonatype Flexmojos Proxy"
  • Remote Storage Location은 "http://repository.sonatype.org/content/groups/flexgroup/"

그림 19.2 Sonatype Flexmojos 프록시 레파지토리 설정

위의 그림과 같이 설정을 했으면, 프록시 레파지토리를 저장하기 위해 Save 버튼을 클릭하고 Sonatype Flexmojos 레파지토 프록싱을 시작한다. Nexus는 public 레파지토리 그룹을 가지고 있느넫, 이는 몇가지 레파지토리들을 Maven 사용자를 위해 접근을 단일화시킨 지점으로 묶는다. 새로운 프록시 레파지토리에 대한 설정을 마무리하기 위해, 이 새로운 프록시 레파지토리를 Nexus public 그룹에 추가해야 한다. 이렇게 하려면, Repositories 패널의 상단에 보이는 repositories 목록으로 되돌아간다. Public Repositories 그룹을 클릭하고 Repository 패널의 하단에 있는 Configuration 탭을 클릭한다. Configuration 탭을 클릭하면 그림 19.3 "Public Repositories 그룹에 Sonatype Flexmojos 프록시 추가" 에 보이는 것과 같이 Group configuration이 나타난다.


그림 19.3 Public Repositories 그룹에 Sonatype Flexmojos 프록시 추가

Sonatype Public Proxy를 Public Repositories에 추가하려면 Available Repositories 목록에 있는 Sonatype Public Proxy 레파지토리를 선택하고 Ordered Group Repositories 목록에 이동시킨다. Save 버튼을 클릭하면 Sonatype Flexmojos 레파지토리에 대한 프록시를 Nexus 에 성공적으로 추가된다. 사용자는 레파지토리 그룹에 있는 산출물을 요청할 때마다, Nexus가 아직 해당 산출물을 캐싱하고 있지 않으면, Sonatype Flexmojos 레파지토리인 http://repository.sonatype.org/content/groups/flexgroup/ 에 질의를 보낸다. Nexus는 Sonatype Flexmojos 레파지토리로부터 가지고 온 모든 산출물에 대한 로컬 캐시를 관리하게 된다. 이 로컬 캐시를 통해 통제와 더 안정된 빌드 환경을 보장받게 된다. 만일 여러명의 개발자들이 Sonatype의 public 레파지토리의 산출물들을 의존하도록 세팅한다면, 필요한 산출물들이 Nexus 인스턴에 의해 캐싱되었으면 Sonatype의 레파지토가 가용하지 않더라도 완전히 자체적으로 포함되는 빌드 환경을 갖추게 된다.

마지막 단계는 방금 설정한 Nexus 인스턴스로 Maven 을 연결하는 것이다. 모든 레파지토리에 대해 미러로 Nexus 레파지토리 그룹을 사용하기 위해 Maven Settings를 변경할 필요가 있다. 이렇게 하려면, ~/.m2/settings.xml 파일에서 다음의 XML을 추가할 필요가 있다.

예제 19.2 로컬 Nexus 인스턴스에 대한 Settings XML
<settings>
  <mirrors>
    <mirror>
      <!--This sends everything else to /public -->
      <id>nexus</id>
      <mirrorOf>*</mirrorOf>
      <url>http://localhost:8081/nexus/content/groups/public</url>
    </mirror>
  </mirrors>
  <profiles>
    <profile>
      <id>nexus</id>
      <!—-Enable snapshots for the built in central repo to direct -->
      <!--all requests to nexus via the mirror -->
      <repositories>
        <repository>
          <id>central</id>
          <url>http://central</url>
          <releases><enabled>true</enabled></releases>
          <snapshots><enabled>true</enabled></snapshots>
        </repository>
      </repositories>
     <pluginRepositories>
        <pluginRepository>
          <id>central</id>
          <url>http://central</url>
          <releases><enabled>true</enabled></releases>
          <snapshots><enabled>true</enabled></snapshots>
        </pluginRepository>
      </pluginRepositories>
    </profile>
  </profiles>
  <activeProfiles>
    <!—-make the profile active all the time -->
    <activeProfile>nexus</activeProfile>
  </activeProfiles>
</settings>
위의 XML은 모든 설정된 레파지토리와 플러그인 레파지토리에 대해 단일 public 레파지티로 그룹을 찾도록 Maven을 설정한다. 산출물에 대한 모든 요청이 Nexus 를 통하도록 보장하는 간단한 방법이다.

19.2.2. Flex 단위 테스트를 지원하는 환경 설정
Flexmojos는 단위 테스트를 실행하는 독자적인 Flash 플레이를 설치할 수 있다. 이와 같이 하려면, PATH에 독자적인 Flash 플레이어를 추가하거나, -DflashPlayer.command 옵션을 사용해서 빌드에 Flash 플레이어 실행의 위치를 넘겨줄 필요가 있다. 단위 테스트를 실행할 때 Flexmojos는 독자적인 Flash 플레이어에 대한 다음의 플랫폼에 특수한 실행 파일을 기대한다.
MS 윈도우
Flexmojos는 FlashPlayer.exe 바이너리를 찾는다. 단위 테스트 실행을 지원하기 위해 PATH에 FlashPlayer.exe를 포함하는 디렉토리를 추가하거나 -DflashPlayer.command=${filepath} 명령행 옵션을 사용해서 Maven에게 FlashPlayer.exe 바이너리의 위치를 넘겨준다.
Macintosh OSX
Flexmojos는 "Flash Palyer" 어플리케이션 위치를 찾는다. 단위 테스트 실행을 지원하려면, PATH에 "Flash Player"를 포함하는 디렉토리를 추가하거나 -DflashPlayer.command=${filepath} 명령행 옵션을 사용해서 Maven에게 실행파일 경로를 넘겨준다.
Unix (Linux, Solaris 등)
Flexmojos는 flashplayer 실행파일을 찾는다. 단위 테스트 실행을 지원하려면, PATH에 flashplayer를 포함하는 디렉토리를 추가하거나 -DflashPlayer.command=${filepath} 명령행 옵션을 사용해서 Maven에게 실행파일의 경로를 넘겨준다.

유의
Linux에서 headless 빌드에서 단위테스트를 실행하려면 X virtual framebuffer (Xvfb)가 설치되어야 한다. Xvfb에 대한 더 상세한 정보는 여기를 참조하라.

만일 Adobe Flash CS4나 Adobe Flex Builder를 사용해서 Flash 애플리케이션을 개발하고 있거나 브라우저에서 플래시 내용을 보고 있다면, PC의 어딘가에 Flash player가 설치되었을 가능성이 있다. Flex 단위 테스트에 대해 이러한 플레이어 중에 하나를 사용하도록 Maven을 설정할 수도 있지만, Flash Player의 디버그 버전에서 실행하도록 설정하기 원할 것이다. 비호환성에 대한 잠재적인 문제를 최소화시키기 위해, 다음의 Flash Player 중에 하나를 다운로드 받아서 로컬 PC에 설치해야 한다. 해당 환경에 대한 독자적인 Flash Player를 다운로드 받는다.
위의 플레이어를 설치하고 OSX 머신에 PATH를 추가하려면, 다음과 같은 명령을 실행한다.
$ wget http://download.macromedia.com/pub/flashplayer/updaters/10/\
flashplayer_10_sa_debug.app.zip
$ unzip flashplayer_10_sa_debug.app.zip
$ sudo cp -r Flash\ Player.app /Applications/
$ export PATH=/Applications/Flash\ Player.app/Contents/MacOS:/usr/local/bin:/usr/local/maven/bin:/usr/kerberos/sbin:/usr/kerberos/bin:/usr/java/
latest/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin:/usr/bin:/usr/local/bin

명령행에 PATH에 Flash Player에 대한 경로를 추가하는 대신에, 환경 프로파일을 설정해야 한다. OSX에서 ~/.bash_profile에 마지막 export 구문을 추가할 수도 있다.

19.2.3. Maven Settings의 Plugin Group에 Flexmojos 추가
명령행에서 Flexmojos goal을 실행할 필요가 있다면, Maven Settings에 Sonatype Plugin 그룹을 추가하면 좀 더 편해질 수 있다. 이와 같이 하려면, ~/.m2/settings.xml 을 열고 다음의 plugin group을 추가한다.

예제 19.3 Maven Settings에 Sonatype Plugin 추가
<pluginGroups>
  <pluginGroup>com.sonatype.maven.plugins</pluginGroup>
  <pluginGroup>org.sonatype.plugins</pluginGroup>
</pluginGroups>

일단 Maven Settings에 위의 플러그인 그룹을 추가했으면, flexmojos 접두어 플러그인을 사용해서 Flexmojos goal을 호출할 수 있다. 이러한 설정이 없으면, flexbuilder goal을 호출하려면 다음과 같은 명령행을 호출해야 한다.
$ mvn org.sonatype.flexmojos:flexmojos-maven-plugin:3.2.0:flexbuilder
Maven Settings에 org.sonatype.plugins 그룹을 설정했다면, 다음과 같이 동일한 goal을 호출할 수 있다.
$ mvn flexmojos:flexbuilder

19.3 Archetype에서 Flexmojos 프로젝트 생성
Flexmojos는 새로운 Flex 프로젝트를 빨리 생성하기 위해 사용될 수 있는 몇가지 archetype들을 가지고 있다. 다음의 archetype들은 모두 org.sonatype.flexmojos 그룹에 있는 3.3.0 버전들이다.
flexmojos-archetypes-library
SWC를 만드는 간단한 Flex Library 프로젝트를 생성
flexmojos-archetypes-application
SWF를 만드는 간단한 Flex 애프리케이션을 생성
flexmojos-archetypes-modular-webapp
WAR를 생성하는 프로젝트에서 궁극적으로 화면에 나타나는 SWF를 만드는 프로젝트가 사용하는 SWC를 만드는 프로젝트로 구성된 다중 모듈 프로젝트를 생성

19.3.1. Flex Library 생성
Flex Library 프로젝트를 생성하려면, 명령행에서 다음의 명령을 실행한다.
$ mvn archetype:generate \
    -DarchetypeRepository=http://repository.sonatype.org/content/groups/public \
    -DarchetypeGroupId=org.sonatype.flexmojos \
    -DarchetypeArtifactId=flexmojos-archetypes-library \
    -DarchetypeVersion=3.3.0
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'archetype'.
[INFO] com.sonatype.maven.plugins: checking for updates from central
...
[INFO] [archetype:generate]
[INFO] Generating project in Interactive mode
[INFO] Archetype defined by properties
...
Define value for groupId: : org.sonatype.test
Define value for artifactId: : sample-library
Define value for version:  1.0-SNAPSHOT: : 1.0-SNAPSHOT
Define value for package:  org.sonatype.test: : org.sonatype.test 
Confirm properties configuration:
groupId: org.sonatype.test
artifactId: sample-library
version: 1.0-SNAPSHOT
package: org.sonatype.test
 Y: : Y[INFO] Parameter: groupId, Value: org.sonatype.test
[INFO] Parameter: packageName, Value: org.sonatype.test
[INFO] Parameter: basedir, Value: /Users/Tim
[INFO] Parameter: package, Value: org.sonatype.test
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: artifactId, Value: sample-library
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL

sample-library/ 디렉토를 보면, 프로젝트가 그림 19.4 "Flexmojo Library Archetype 파일 구조"에 나타나는 디렉토리 구조로 구성된다는 것을 볼 수 있을 것이다.

Flexmojo Library Archetype File Structure
그림 19.4 Flexmojo Library Archetype 파일 구조

간단한 Flex 라이브러리 archetype은 POM, 하나의 소스와 단위 테스트의 세개의 파일만을 포함하고 있다. 이들 각각의 파일에 대해서 살펴보자. 먼저 POM (Project Object Model)

예제 19.4 Flex Library Archetype에 대한 Project Object Model
<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.test</groupId>
  <artifactId>sample-library</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>swc</packaging>
  <name>test Flex</name>
  <build>
    <sourceDirectory>src/main/flex</sourceDirectory>
    <testSourceDirectory>src/test/flex</testSourceDirectory>
    <plugins>
      <plugin>
        <groupId>org.sonatype.flexmojos</groupId>
        <artifactId>flexmojos-maven-plugin</artifactId>
        <version>3.3.0</version>
        <extensions>true</extensions>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>com.adobe.flex.framework</groupId>
      <artifactId>flex-framework</artifactId>
      <version>3.2.0.3958</version>
      <type>pom</type>
    </dependency>
    <!-- flexmojos Unit testing support -->
    <dependency>
      <groupId>org.sonatype.flexmojos</groupId>
      <artifactId>flexmojos-unittest-support</artifactId>
      <version>3.3.0</version>
      <type>swc</type>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>


예제 19.4의 "Flex Library Archetype에 대한 Project Object Model"은 매우 단순하며, 이 POM의 핵심은 flexmojos-maven-plugin 설정의 extensions가 true로 세팅된 것이다. 이 설정은 flexmojos-maven-plugin에서 정의된 swc 패키징에 대한 생명주기를 바꾼다. 그 다음에 archetype은 flex-framework 의존관계와 flexmojos-unittest-support 테스트 영역의 의존관계를 포함한다. flex-framework 의존관계는 SWC 라이브러리들과 Flex 애플리케이션을 컴파일하는데 필요한 리소스들에 대한 참조를 포함하는 POM이다.
예제 19.4 "Flex Library Archetype에 대한 Project Object Model"에서 packaging은 매우 중요하다. POM의 packaging 타입은 빌드 결과를 만드는데 사용되는 생명주기를 조정한다. packaging 요소에 있는 swc 값은 flexmojos-maven-pluging이 제공하는 Flex에 특화된 생명주기를 검색하는 Maven의 특별한 선언이다. 위의 POM의 또 다른 중요한 부분은 Flex 소스 코드와 Flex 단위 테스트의 위치를 지정하는 build 요소이다. 다음에는 이 archetype이 만든 예제 Actionscript를 포함하고 있는 예제 19.5 "Flex Library Archetype의 예제 애플리케이션 클래스"를 간단하게 살펴보자.

예제 19.5. Flex Library Archetype의 예제 애플리케이션 클래스
package org.sonatype.test {
  public class App {
    public static function greeting(name:String):String {
      return "Hello, " + name;
    }
  }
}
위의 코드는 단순하지만, "여기에 코드를 입력하시오"와 같은 간략하고 빠른 틀을 제공한다. 이러한 간단한 코드를 테스트하는 것인 바보같이 보일 수 있지만, TestApp.as 라는 예제 테스트가 src/test/flex 디렉토리에 제공된다. 이 테스트는 예제 19.6 "Library Archetype의 App 클래스에 대한 단위 테스트"에 나타나있다.

예제 19.6 Library Archetype의 App 클래스에 대한 단위 테스트
package org.sonatype.test {
  import flexunit.framework.TestCase;
  public class TestApp extends TestCase {
    /**
     * Tests our greeting() method
     */
    public function testGreeting():void {
      var name:String = "Buck Rogers";
      var expectedGreeting:String = "Hello, Buck Rogers";
      var result:String = App.greeting(name);
      assertEquals("Greeting is incorrect", expectedGreeting, result);
    }
  }
}

이 빌드를 실행하려면, sample-library 프로젝트 디렉토리로 가서 mvn install을 실행한다.
$ mvn install
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building sample-library Flex
[INFO]    task-segment: [install]
[INFO] ------------------------------------------------------------------------
[INFO] [resources:resources]
[INFO] [flexmojos:compile-swc]
[INFO] flexmojos 3.3.0 - GNU GPL License (NO WARRANTY) - \
See COPYRIGHT file
[WARNING] Nothing expecified to include.  Assuming source and resources folders.
[INFO] Flex compiler configurations:
-compiler.headless-server=false
-compiler.keep-all-type-selectors=false
-compiler.keep-generated-actionscript=false
-compiler.library-path ~/.m2/repository/com/adobe/flex/framework/flex/\
3.2.0.3958...
-compiler.namespaces.namespace http://www.adobe.com/2006/mxml
        target/classes/configs/mxml-manifest.xml
-compiler.optimize=true
-compiler.source-path src/main/flex
...
[INFO] [resources:testResources]
[WARNING] Using platform encoding (MacRoman actually) to copy filtered \
        resources, i.e.  build is platform dependent!
[INFO] skip non existing resourceDirectory src/test/resources
[INFO] [flexmojos:test-compile]
[INFO] flexmojos 3.3.0 - GNU GPL License (NO WARRANTY) - \
See COPYRIGHT file
[INFO] Flex compiler configurations:
-compiler.include-libraries ~/.m2/repository/org/sonatype/flexmojos/\
        flexmojos-unittest-support...
-compiler.keep-generated-actionscript=false
-compiler.library-path ~/.m2/repository/com/adobe/flex/framework/flex
        3.2.0.3958/flex-3.2.0....
-compiler.optimize=true
-compiler.source-path src/main/flex target/test-classes src/test/flex
-compiler.strict=true
-target-player 9.0.0
-use-network=true
-verify-digests=true -load-config=
[INFO] Already trust on target/test-classes/TestRunner.swf
[INFO] [flexmojos:test-run]
[INFO] flexmojos 3.3.0 - GNU GPL License (NO WARRANTY) - \
See COPYRIGHT file
[INFO] flexunit setup args: null
[INFO] ------------------------------------------------------------------------
[INFO] Tests run: 1, Failures: 0, Errors: 0, Time Elpased: 0 sec
[INFO] [install:install]

주의
Flex 단위 테스트를 실행하려면 Flash Player가 포함된 PATH 환경 변수를 설정할 필요가 있다. 단위 테스트에 대한 Flexmojos 설정에 대한 더 자세한 정보는 19.2.2의 "Flex 단위 테스트를 지원하는 환경 설정"을 참고하라.
위의 프로젝트에 대한 mvn install을 실행하면 Maven과 Flexmojos 플러그인이 Flex 컴파일러에 대한 모든 라이브러리와 의존관계를 관리하고 있는 결과에 주목해야 한다. Maven이 자바 클래스패스의 내용을 관리하도록 자바 개발자를 도우는데 뛰어난 것처럼, Flex 개발자들을 위해 컴파일 경로에 대한 복잡성을 관리하는데 도움이 될 수 있다. Flexmojos 프로젝트가 웹 브라우저나 Flash Player를 기동하고 프로젝트의 소스 코드에 대한 TestApp.as 클래스를 실행하는데 사용하는 것에 놀랄 수도 있을 것이다.

19.3.2. Flex 애플리케이션 생성
Maven archetype에서 Flex 애플리케이션을 생성하려면, 다음과 같은 명령을 실행한다.
$ mvn archetype:generate \
    -DarchetypeRepository=http://repository.sonatype.org/content/groups/public \
    -DarchetypeGroupId=org.sonatype.flexmojos \
    -DarchetypeArtifactId=flexmojos-archetypes-application \
    -DarchetypeVersion=3.3.0

[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'archetype'.
[INFO] com.sonatype.maven.plugins: checking for updates from central
...
[INFO] [archetype:generate]
[INFO] Generating project in Interactive mode
[INFO] Archetype defined by properties
...
Define value for groupId: : org.sonatype.test
Define value for artifactId: : sample-application
Define value for version:  1.0-SNAPSHOT: : 1.0-SNAPSHOT
Define value for package:  org.sonatype.test: : org.sonatype.test
Confirm properties configuration:
groupId: org.sonatype.test
artifactId: sample-library
version: 1.0-SNAPSHOT
package: org.sonatype.test
 Y: : Y
[INFO] Parameter: groupId, Value: org.sonatype.test
[INFO] Parameter: packageName, Value: org.sonatype.test
[INFO] Parameter: basedir, Value: /Users/Tim/flex-sample
[INFO] Parameter: package, Value: org.sonatype.test
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: artifactId, Value: sample-application
[INFO] BUILD SUCCESSFUL

sample-application/ 디렉토리를 보면, 그림 19.5의 "Flex 애플리케이션 Archetype에 대한 디렉토리 구조"에 나타난 파일 구조를 볼 수 있을 것이다.

Directory Structure for Flex Application Archetype
그림 19.5 Flex 애플리케이션 Archetype에 대한 디렉토리 구조

애플리케이션 archetype으로 만들어진 애플리케이션을 만들면 다음과 같은 POM이 만들어진다.

예제 19.7. Flex 애플리케이션 Archetype에 대한 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.test</groupId>
  <artifactId>sample-application</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>swf</packaging>
  <name>sample-application Flex</name>
  <build>
    <sourceDirectory>src/main/flex</sourceDirectory>
    <testSourceDirectory>src/test/flex</testSourceDirectory>
    <plugins>
      <plugin>
        <groupId>org.sonatype.flexmojos</groupId>
        <artifactId>flexmojos-maven-plugin</artifactId>
        <version>3.3.0</version>
        <extensions>true</extensions>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>com.adobe.flex.framework</groupId>
      <artifactId>flex-framework</artifactId>
      <version>3.2.0.3958</version>
      <type>pom</type>
    </dependency>
    <!-- flexmojos Unit testing support -->
    <dependency>
      <groupId>org.sonatype.flexmojos</groupId>
      <artifactId>flexmojos-unittest-support</artifactId>
      <version>3.3.0</version>
      <type>swc</type>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>


예제 19.7의 "Flex 애플리케이션 Archetype에 대한 POM"과 예제 19.4의 "Flex Library Archetype에 대한 Project Object Model"과의 차이는 packaging 요소가 swc 대신에 swf 라는 것이다. packaging을 swf로 세팅함으로써 프로젝트는 target/sample-application-1.0-SNAPSHOT.swf에 Flex 애플리케이션을 만들게 된다. 이 archetype으로 만들어진 예제 애플리케이션은 "Hello World"라는 문자를 보여준다. Main.mxml이 src/main/flex에 있다.

예제 19.8. 예제 애플리케이션 Main.mxml
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
        <mx:Text text="Hello World!"/>
</mx:Application>


위 애플리케이션은 메시지 트레이스를 출력하는 것만 하는 FlexUnit 테스트를 만든다. 예제 단위 테스트가 src/test/flex/org/sonatype/test에 있다.

예제 19.9. Main.mxml에 대한 단위 테스트
package org.sonatype.test
{
        import flexunit.framework.TestCase;
        import Main;
        public class TestApp extends TestCase
        {
                public function testNothing():void
                {
                        //TODO un implemented
                        trace("Hello test");
                }
        }
}

19.3.3. 다중 모듈 프로젝트 생성 : Flex 의존관계를 가지는 웹 애플리케이션
Web 애플리케이션이 참조하는 Flex 애플리케이션과 다시 Flex 애플리케이션이 참조하는 Flex Library 프로젝트를 가지는 다중 모듈 프로젝트를 만들려면 다음과 같이 수행한다.
$ mvn archetype:generate \
    -DarchetypeRepository=http://repository.sonatype.org/content/groups/public \
    -DarchetypeGroupId=org.sonatype.flexmojos \
    -DarchetypeArtifactId=flexmojos-archetypes-modular-webapp \
    -DarchetypeVersion=3.3.0

[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'archetype'.
[INFO] com.sonatype.maven.plugins: checking for updates from central
...
[INFO] [archetype:generate]
[INFO] Generating project in Interactive mode
[INFO] Archetype defined by properties
...
Define value for groupId: : org.sonatype.test
Define value for artifactId: : sample-multimodule
Define value for version:  1.0-SNAPSHOT: : 1.0-SNAPSHOT
Define value for package:  org.sonatype.test: : org.sonatype.test
Confirm properties configuration:
groupId: org.sonatype.test
artifactId: sample-library
version: 1.0-SNAPSHOT
package: org.sonatype.test
 Y: : Y
[INFO] Parameter: groupId, Value: org.sonatype.test
[INFO] Parameter: packageName, Value: org.sonatype.test
[INFO] Parameter: basedir, Value: /Users/Tim
[INFO] Parameter: package, Value: org.sonatype.test
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] Parameter: artifactId, Value: sample-multimodule
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
sample-multimodule/ 디렉토리를 살펴보면, swc, swf, war의 세개의 프로젝트를 포함하는 디렉토리 구조를 볼 수 있을 것이다.
Directory Structure for Flex Multimodule Archetype
그림 19.6. Flex 멀티모듈 archetype에 대한 디렉토리 구조

이 다중 모듈 프로젝트의 간단한 최상위 POM이 나타나있다. swc, swf, war 모듈을 참조하는 모듈로 구성한다.

예제 19.10. 모듈화된 웹 애플리케이션 archetype에 의해 생성된 최상위 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.test</groupId>
  <artifactId>sample-multimodule</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>pom</packaging>
 
  <modules>
    <module>swc</module>
    <module>swf</module>
    <module>war</module>
  </modules>
</project>

swc 프로젝트는 예제 19.4의 "Flex Library Archetype에 대한 Project Object Model"에 나타난 POM과 유사한 간단한 POM을 가진다. 이 POM의 artifactId가 모듈 디렉토리의 이름과 다른 swc-swc 임을 유의하라.

예제 19.11. swc 모듈 POM
<project>
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.sonatype.test</groupId>
    <artifactId>sample-multimodule</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <groupId>org.sonatype.test</groupId>
  <artifactId>swc-swc</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>swc</packaging>
  <name>swc Library</name>
  <build>
    <sourceDirectory>src/main/flex</sourceDirectory>
    <testSourceDirectory>src/test/flex</testSourceDirectory>
    <plugins>
      <plugin>
        <groupId>org.sonatype.flexmojos</groupId>
        <artifactId>flexmojos-maven-plugin</artifactId>
        <version>3.3.0</version>
        <extensions>true</extensions>
        <configuration>
          <locales>
            <locale>en_US</locale>
          </locales>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>com.adobe.flex.framework</groupId>
      <artifactId>flex-framework</artifactId>
      <version>3.2.0.3958</version>
      <type>pom</type>
    </dependency>
    <!-- flexmojos Unit testing support -->
    <dependency>
      <groupId>org.sonatype.flexmojos</groupId>
      <artifactId>flexmojos-unittest-support</artifactId>
      <version>3.3.0</version>
      <type>swc</type>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

swf 모듈의 POM은 예제 19.7의 "Flex 애플리케이션 archetype에 대한 POM"과 유사하며 swc-swc 산출물에 대한 의존관계가 추가되었다. 다음의 POM은 모듈을 저장하는 디렉토리와 다른 artifactId를 정의하고 있음을 유의하라. POM에 있는 artifactId는 swf-swf이다.

예제 19.12. swf 모듈 POM
<project>
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.sonatype.test</groupId>
    <artifactId>sample-multimodule</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <groupId>org.sonatype.test</groupId>
  <artifactId>swf-swf</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>swf</packaging>
  <name>swf Application</name>
  <build>
    <sourceDirectory>src/main/flex</sourceDirectory>
    <testSourceDirectory>src/test/flex</testSourceDirectory>
    <plugins>
      <plugin>
        <groupId>org.sonatype.flexmojos</groupId>
        <artifactId>flexmojos-maven-plugin</artifactId>
        <version>3.3.0</version>
        <extensions>true</extensions>
        <configuration>
          <locales>
            <locale>en_US</locale>
          </locales>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>com.adobe.flex.framework</groupId>
      <artifactId>flex-framework</artifactId>
      <version>3.2.0.3958</version>
      <type>pom</type>
    </dependency>
    <!-- flexmojos Unit testing support -->
    <dependency>
      <groupId>org.sonatype.flexmojos</groupId>
      <artifactId>flexmojos-unittest-support</artifactId>
      <version>3.3.0</version>
      <type>swc</type>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.sonatype.test</groupId>
      <artifactId>swf-swc</artifactId>
      <version>1.0-SNAPSHOT</version>
      <type>swc</type>
    </dependency>
  </dependencies>
</project>

주의
예제 19.12의 "swf 모듈 POM"에서 "swf-swc"에 대한 의존관계는 "swc-swc"로 변경해야 한다. 이는 FlexMojos 3.3.0 릴리즈 버전에 있는 모듈화 webapp archetype의 버그이다. FlexMojos 3.4.0 릴리즈에서는 수정될 것이다.

SWC에 대한 의존관계를 선언할 때 의존관계에 대한 유형을 지정할 필요가 있는데, 그럼으로써 Maven은 원격 혹은 로컬 레파지토리에 적합한 산출물을 위치시킬 수 있다. 이 경우 swf-swf 프로젝트는 swc-swc 프로젝트가 생성한 SWC를 의존하고 있다. swf-swf 프로젝트에 대한 의존관계를 추가할 때 FlexMojos 플러그인은 Flex 컴파일러의 라이브러리 경로에 적절한 SWC 파일을 추가하게 된다.

다음엔 war 모듈에 있는 간단한 POM을 살펴보자.

예제 19.13. war 모듈 POM
<project>
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <artifactId>sample-multimodule</artifactId>
    <groupId>org.sonatype.test</groupId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  <groupId>org.sonatype.test</groupId>
  <artifactId>war-war</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>
  <build>
    <plugins>
      <plugin>
        <groupId>org.sonatype.flexmojos</groupId>
        <artifactId>flexmojos-maven-plugin</artifactId>
        <version>3.3.0</version>
        <executions>
          <execution>
            <goals>
              <goal>copy-flex-resources</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>maven-jetty-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>org.sonatype.test</groupId>
      <artifactId>war-swf</artifactId>
      <version>1.0-SNAPSHOT</version>
      <type>swf</type>
    </dependency>
  </dependencies>
</project>

주의
예제 19.13의 "war 모듈 POM"에서 "war-swf"에 대한 의존관계는 "swf-swf"로 바꾸어야 한다. 이는 FlexMojos 3.3.0 릴리즈에 있는 모듈화 webapp archetype의 버그이다. FlexMojos 3.4.0 릴리즈에서 수정될 것이다.

예제 19.13의 "war 모듈 POM"에 나타난 POM은 이 프로젝트의 copy-flex-resources goal을 실행하기 위한 FlexMojos 플러그인을 설정한다. copy-flex-resources goal은 SWF 애플리케이션을 웹 애플리케이션의 문서 루트에 복사하게 된다. 이 프로젝트에서 WAR를 빌드하고 생성하면 swf-swf-1.0-SNAPSHOT.swf 파일이 target/war-war-1.0-SNAPSHOT에 있는 웹 애플리케이션의 루트 디렉토리에 복사된다.
다중 모듈 웹 애플리케이션 프로젝트를 빌드하려면 최상위 디렉토리에서 mvn install을 실행한다. 이를 실행하면 swc-swc, swf-swf, war-war 산출물을 빌드하고 웹 애플리케이션의 문서 루트에 swf-swf-1.0-SNAPSHOT.swf를 포함하는 war/target/war-war-1.0-SNAPSHOT.war 에 WAR 파일을 생성한다.

유의
Flex 단위 테스트를 실행하려면 Flash Player를 포함하게끔 PATH 환경 변수를 설정해야 한다. 단위 테스트에 대한 FlexMojos 설정에 대한 더 자세한 정보는 19.2.2 "Flex 단위 테스트를 지원하는 환경 설정"을 참고하라.

19.4. FlexMojos 생명주기
FlexMojos Maven 플러그인은 packaging 을 기반으로 생명주기를 변경한다. 만일 프로젝트가 swc나 swf의 packaging을 가진다면, FlexMojos 플러그인은 플러그인 설정이 true로 되어 있으면 변경된 생명주기를 실행한다. 예제 19.14의 "변경 Flex 생명주기를 위한 플러그인 extensions 세팅"에서 extensions를 true로 세팅한 flexmojos-maven-plugin에 대한 플러그인 설정을 보여주고 있다.

예제 19.14. 변경 Flex 생명주기를 위한 플러그인 extensions 세팅
  <build>
    <sourceDirectory>src/main/flex</sourceDirectory>
    <testSourceDirectory>src/test/flex</testSourceDirectory>
    <plugins>
      <plugin>
        <groupId>org.sonatype.flexmojos</groupId>
        <artifactId>flexmojos-maven-plugin</artifactId>
        <version>3.3.0</version>
        <extensions>true</extensions>
        <configuration>
          <locales>
            <locale>en_US</locale>
          </locales>
        </configuration>
      </plugin>
    </plugins>
  </build>


19.4.1 SWC 생명주기
packaging이 swc일 때 FlexMojos는 그림 19.7의 "FlexMojos SWC 생명주기"에 나타난 생명주기를 실행한다. 색깔이 다른 goal이 FlexMojos 플러그인의 goal들이며, 흰색의 goal은 핵심 Maven 플러그인의 표준 goal을 나타낸다.
The FlexMojos SWC Lifecycle
그림 19.7 FlexMojos SWC 생명주기

FlexMojos의 goal은 다음과 같다.
flexmojos:compile-swc
이 goal은 sourceDirectory에 있는 모든 ActionScript와 MXML을 SWC로 컴파일을 한다. SWC는 Flex 애플리케이션에서 사용되는 컴포넌트와 리소스를 포함하는 Adobe 라이브러리이거나 클래스 파일이다.
flexmojos:test-compile
이 goal은 testSourceDirectory에 있는 모든 ActionScript와 MXML 파일을 컴파일한다.
flexmojos:test-run
이 goal은 Flash Player를 사용해서 단위테스트를 실행한다. 이 goal은 19.2.2의 "Flex 단위테스트를 지원하는 환경 설정"에서 설명된 대로 Flash Player가 설정되었다면 실행시킬 수 있다.

19.4.2 SWF 생명주기
packaging이 swf 이면, FlexMojos는 그림 19.8의 "FlexMojos SWF 생명주기"에서 보이는 것과 같이 해당 생명주기를 실행한다. 색깔이 다른 goal들이 FlexMojos 플러그인의 goal이며, 흰색의 goal들이 핵심 Maven 플러그인의 goal들이다.

The FlexMojos SWF Lifecycle
그림 19.8 FlexMojos SWF 생명주기

FlexMojos의 goal은 다음과 같다.
flexmojos:compile-swf
이 goal은 sourceDirectory의 모든 ActionScript와 MXML 파일들을 SWF로 컴파일한다. SWF는 Adobe Flash Player나 Adobe AIR 소프트웨어에서 실행되는 애플리케이션을 포함하는 파일이다.
flexmojos:test-compile
이 goal은 testSourceDirectory의 모든 ActionScript와 MXML 파일을 컴파일한다.
flexmojos:test-run
이 goal은 Flash Player를 사용해서 단위테스트를 실행한다. 이 goal은 Flash Player가 19.2.2 "Flex 단위테스트를 지원하기 위한 환경 설정"에 설명된 것과 같이 설정되었다면 실행할 수 있다.

19.5 FlexMojos 플러그인 goal
FlexMojos Maven 플러그인은 다음과 같은 goal들을 포함한다.
flexmojos:asdoc
ActionScript 소스 파일에 대한 문서 생성
flexmojos:asdoc-report
Maven 사이트에 포함될 수 있는 보고서 형태로 ActionScript 소스에 대한 문서 생성
flexmojos:compile-swc
Flex나 AIR 애플리케이션에서의 사용을 위한 Flex 소스(ActionScript와 MXML)을 SWC 라이브러리로 컴파일
flexmojos:compile-swf
Adobe Flash Player나 Adobe AIR 실행에서의 사용을 위한 Flex 소스(ActionScript와 MXML)을 SWF로 컴파일
flexmojos:copy-flex-resources
웹 애플리케이션 프로젝트로 Flex 리소스 복사
flexmojos:flexbuilder
Adobe Flex Builder에서의 사용을 위한 프로젝트 생성
flexmojos:generate
Granite GAS3를 사용해서 Java 클래스 기반의 ActionScript 3 생성
flexmojos:optimize
swc 파일에 대한 post-linke SWF 최적화를 실행하는 goal. 이 goal은 RSL 파일을 생성하는데 사용
flexmojos:sources
Flex 프로젝트에 대한 모든 소스를 포함하는 JAR 생성
flexmojos:test-compile
Flex 프로젝트에 있는 모든 테스트 클래스 컴파일
flexmojos:test-run
Adobe Flash Player를 사용한 테스트 실행
flexmojos:test-swc
특정 프로젝트에 대한 테스트 클래스를 포함하는 SWC 빌드
flexmojos:wrapper
SWF 애플리케이션에 대한 HTML 랩퍼 생성

19.5.1 ActionScript 문서 생성
ActionScript를 위한 문서를 생성하는 asdoc이나 asdoc-report goal을 실행할 수 있다. 일단 goal이 실행되었으면, 문서는 ${basedir}/target/asdoc으로 HTML 형태로 저장된다. 그림 19.9의 "FlexMojos 플러그인에 의해 생성된 ActionScript 문서"는 FlexMojos 애플리케이션 archetype 프로젝트에 대한 asdoc을 실행한 결과를 보여준다.
Actionscript Documentation Generated by the FlexMojos Plugin
그림 19.9 FlexMojos 플러그인에 의해 생성된 ActionScript 문서

19.5.2. Flex 소스 컴파일
FlexMojos는 ActionScript와 MXML을 SWC와 SWF로 컴파일하는 많은 goal들을 포함한다. compile-swc와 compile-swf goal은 프로젝트의 소스로부터 결과물을 생성하는데 사용되며, test-compile은 단위테스트를 컴파일하는데 사용된다. FlexMojos archetype에 의해 생성된 간단한 프로젝트에서 compile-swc와 compile-swf goal이 프로젝트가 생명주기를 변경하고 compile 단계에 compile-swc나 compile-swf를 엮고 test-compile 단계에 test-compile 를 엮었기 때문에 호출된다. 만일 FlexMojos 컴파일러에 대한 옵션을 설정할 필요가 있다면, FlexMojos 플러그인 설정을 수행할 수 있다. 예를 들어, 예제 19.7의 "Flex 애플리케이션 Archetype에 대한 POM"에 나타난 POM을 사용한 애플리케이션에서 컴파일시 코드 수준의 경고를 모시하고 특정 폰트 세팅을 사용하고자 원한다면, 예제 19.15의 "Compiler 플러그인 변경"에 나타나 플러그인 configuration을 사용할 수 있다.

예제 19.15 Compile 플러그인 변경
<build>
    <sourceDirectory>src/main/flex</sourceDirectory>
    <testSourceDirectory>src/test/flex</testSourceDirectory>
    <plugins>
      <plugin>
        <groupId>org.sonatype.flexmojos</groupId>
        <artifactId>flexmojos-maven-plugin</artifactId>
        <version>3.3.0</version>
        <extensions>true</extensions>
        <configuration>
          <configurationReport>true</configurationReport>
          <warnings>
            <arrayTostringChanges>true</arrayTostringChanges>
            <duplicateArgumentNames>false</duplicateArgumentNames>
          </warnings>
          <fonts>
            <advancedAntiAliasing>true</advancedAntiAliasing>
            <flashType>true</flashType>
            <languages>
              <englishRange>U+0020-U+007E</englishRange>
            </languages>
            <localFontsSnapshot>
              ${basedir}/src/main/resources/fonts.ser
            </localFontsSnapshot>
            <managers>
              <manager>flash.fonts.BatikFontManager</manager>
            </managers>
            <maxCachedFonts>20</maxCachedFonts>
            <maxGlyphsPerFace>1000</maxGlyphsPerFace>
          </fonts>
        </configuration>
      </plugin>
    </plugins>
  </build>


19.5.3 Flex Builder 프로젝트 파일 생성
FlexMojos 프로젝트에 대한 Flex Builder 프로젝트 파일을 생성하려면, 19.2.3 "Maven Settings의 Plugin Group에 FlexMojos 추가"에 설명된 대로 플러그인 그룹을 설정하고, flexbuilder goal을 실행한다.
$ mvn flexmojos:flexbuilder

위의 goal을 실행하면 다음과 같은 것들이 생성된다.
.project, .settings/org.eclipse.core.resources.prefs, .actionScriptProperties, .flexLibProperties

19.6 FlexMojos 플러그인 보고서
FlexMojos Maven 플러그인은 다음과 같은 보고서를 포함한다.
flexmojos:asdoc-report
Maven 사이트에 포함될 수 있는 레포트로 ActionScript 소스에 대한 문서 생성

19.6.1 ActionScript 문서 레포트 생성
Maven 사이트 빌드의 일부로 asdoc-report를 생성하려면 다음의 XML을 POM에 추가한다.

예제 19.16. ActionScript 문서 레포트 설정
<reporting>
  <plugins>
    <plugin>
      <groupId>org.sonatype.flexmojos</groupId>
      <artifactId>flexmojos-maven-plugin</artifactId>
      <version>3.3.0</version>
      <reportSets>
        <reportSet>
          <id>flex-reports</id>
          <reports>
            <report>asdoc-report</report>
          </reports>
        </reportSet>
      </reportSets>
    </plugin>
  </plugins>
</reporting>


mvn site를 실행할때 Maven은 위의 레포트를 생성하고 그림 19.10 "Maven Site의 ActionScript 문서 레포트"에서 보이는 것과 같이 "Project Reports"에 그 결과를 위치시킨다.
Actionscript Documentation Report on Maven Site
그림 19.10. Maven Site의 ActionScript 문서 레포트

만일 asdoc-report에 대해 configuration 옵션을 넘길 필요가 있다면, 예제 19.17의 "asdoc-report 설정"에 나타난 바와 같이 reportSets 요소에 설정 요소를 추가할 필요가 있다.

예제 19.17 asdoc-report 설정
<reporting>
    <plugins>
      <plugin>
        <groupId>org.sonatype.flexmojos</groupId>
        <artifactId>flexmojos-maven-plugin</artifactId>
        <version>3.3.0</version>
        <reportSets>
          <reportSet>
            <id>flex-reports</id>
            <reports>
              <report>asdoc-report</report>
            </reports>
            <configuration>
              <windowTitle>My TEST API Doc</windowTitle>
              <footer>Copyright 2009 Sonatype</footer>
            </configuration>
          </reportSet>
        </reportSets>
      </plugin>
    </plugins>
  </reporting>


19.7 Flexmojos 개발과 커스터마이징
다음은 Flexmojos 프로젝트에 대해 변경하거나 공헌하는 첫번째 단계로써 설명하는 내용이다. Flexmojos는 ActionScript를 SWF와 SWC 산출물로 컴파일하는데 관심이 있는 사람들을 위한 도구 이상의 것이며, 개발자들의 커뮤니티이다. 다음의 내용은 모든 사람을 위한 것은 아니지만, 해당 내용을 살펴보고 참여하기 원한다면, 동참하기 바란다.

19.7.1 Flexmojos 소스 코드 얻기
Flexmojos는 Sonatype Forge에서 관리되는 오픈 소스 프로젝트이며, Flexmojos의 소스 코드는 Sonatype Forge의 Subversion 레파지토리에 저장되어 있다. 웹 브라우저로 http://svn.sonatype.org/flexmojos/trunk 로 이동해서 Flexmojos Subversion 레파지토리의 내용을 볼 수 있다.
Flexmojos Subversion Repository
그림 19.11 Flexmojos Subversion 레파지토리

만일 Flexmojos 프로젝트에 참여하기를 희망하는 사람들은 로컬 PC에 현재의 Flexmojos 소스 코드를 체크아웃하기 원할 것이다. Subversion을 사용해서 Flexmojos 소스를 체크아웃하려면 다음과 같이 명령행에 해당 명령을 실행한다.
$ svn co http://svn.sonatype.org/flexmojos/trunk flexmojos
A flexmojos
...
$ ls
COPYRIGHT               flexmojos-sandbox         pom.xml
flexmojos-archetypes    flexmojos-super-poms      src
flexmojos-maven-plugin  flexmojos-testing
flexmojos-parent        flexmojos-touchstone

저작자 표시 비영리 동일 조건 변경 허락
신고
크리에이티브 커먼즈 라이선스
Creative Commons License

3부: Maven 참고
Maven은 여러 도움이 되는 참고 소개자료 이상을 필요로 한다. 이 절은 이해가 되는 참고 자료를 제공한다.

8.1. 소개
7장에서 전체적인 기능 다중 모듈 빌드를 만드는데 여러 Maven들이 어떻게 같이 작용하는지를 살펴보았다. 해당 장의 예제가 실제 어플리케이션을 제시했지만 – DB와 상호작용하는 것, 두개의 인터페이스를 제공하는 웹 서비스, 웹 어플리케이션, 명령행에 대한 프로젝트 - 해당 에제 프로젝트는 여전히 예시용이다. 실제 프로젝트의 복잡성을 표현하려면 현재 읽고 있는 것보다 더 많은 분량의 책이 필요할 것이다. 실세계의 어플리케이션은 수년 동안 변화하며 많은 여러 팀의 개발자에 의해서 각각 서로 다른 초점을 가지면서 종종 운영된다. 실세계 프로젝트에서 다른 사람의 의사 결정과 만든 설계를 종종 평가하게 된다. 이 장에서 2부에서 보았던 예제로 다시 돌아가서 단계를 살펴보고, 지금 Maven에 대해 알고 있는 것이 주어진 상황에서 더 이해할 수도 있는 최적화가 있는지를 스스로 자문할 것이다. Maven은 필요에 따라 단순하거나 복잡할 수 있는 매우 강력한 도구이다. 이러한 이유로 동일한 작업을 수행하는데 수백만 가지의 방법이 있을 수도 있으며, Maven 프로젝트를 설정하는데 “적합한” 방법이 전혀 없을 수도 있다.
마지막 문장을 Maven이 적합하게 만들어졌는지를 결정하는 열쇠로 잘못 해석하지 마라. 비록 Maven은 접근법에 대한 다양성을 허락하지만, 분명 “Maven 방식”이 있으며, 사용되는데 맞게 설계되었기 때문에 Maven을 사용해서 더 생산적으로 작업할 수 있다. 이 장에서 하려는 것은 기존 Maven 프로젝트에 대해서 수행할 수 있는 최적화의 몇가지를 알아보는 것이다. POM 설계에 대한 교육은 효율적으로 POM을 설계하는 매우 어려운 요구사항이다. pom.xml에 프로필을 선언하는 것보다 ~/.m2/settings.xml의 특정 세팅을 정의하는 것이 물론 더 쉽지만, 책을 쓴다는 것은 독자가 준비가 되기 전에 개념을 소개하지 않는다는 것에 직면하고 보장하는 것과 대부분 관련된다. 2부에서 너무나 많은 정보로 인해 독자들을 당황하게 하지 않게 하려고 했으며, 그렇게 함으로써 이 장의 이후에 소개된 <dependencyManagement> 요소와 같은 몇가지 핵심 개념을 건너뛰었다.
이책의 저자들이 특정 장의 주요 요점에 대해 얼버무리기 위해 중요한 세부사항을 간단하게 소개하거나 주석을 달때 2부에는 많은 예제들이 있다. Maven 프로젝트를 어떻게 만드는지 배웠으며, 독자에게 사용가능한 매번 마지막 스위치와 다이어를 소개하는 수백 페이지를 겪지 않고 컴파일하고 인스톨했다. 새로운 Maven 사용자에게 매우 길고, 겉으로 지루하게 긴 내용을 통해 두서없이 진행되는 것보다는 더 빠른 결과를 전달하는게 중요하다고 생각되기 때문에 이와 같이 진행했다. 일단 Maven사용을 시작했다면, 자신의 프로젝트와 POM을 어떻게 분석하는지를 알아야만 한다. 이 장에서 뒤로 되돌아가서 7장의 예제 이후로 남겨진 것이 무엇인지를 살펴본다.

8.2 POM 정리
다중 모듈 프로젝트의 POM을 최적화한다는 것은 집중해야 하는 많은 분야가 있기 때문에 몇가지 방법에서 가장 잘 수행된다. 일반적으로 POM 내와 형제 POM 간의 반복을 살펴본다. 시작할 때 혹은 프로젝트가 여전히 급격하게 진행될 때, 여기 저기에 의존관계와 플러그인 설정이 중복되는 것을 받아들일 수 있지만, 프로젝트가 성숙되고 모듈의 수가 증가함에 따라 공통 의존관계와 설정 지점을 리팩토링할 시간을 갖기를 원할 것이다. POM을 더 효율적으로 만드는 것은 프로젝트가 진행됨에 따라 복잡성을 관리하는데 도움을 주는 긴 방법을 진행할 것이다. 어떤 정보들의 중복이 있을때마다 보통은 더 좋은 방법이 존재한다.

8.3 의존관계 최적화
7장에서 만들어진 다양한 POM을 살펴본다면 복제에 대한 몇가지 패턴에 주목하라. 찾아볼 수 있는 첫번째 패턴은 spring과 hibernate-annotation과 같은 의존관계들은 몇가지 모듈에 선언된다는 것이다. Hibernate 의존관계는 또한 각각의 정의에 복제되는 javax.transaction 에 대한 제외를 하고 있다. 주목할 중복에 대한 두번째 패턴은 때로 몇몇 의존관계들은 관련이 있으며 동일한 버전을 공유한다는 것이다. 이는 프로젝트의 릴리즈가 몇 개의 밀접하게 결합된 컴포넌트들로 구성될 때의 경우이다. 예를 들어, hibernate-annotions와 hibernate-commons-annotions에 대한 의존관계를 살펴보라. 두가지는 모두 버전 3.3.0.ga로 표기되었으며 이 두개의 의존관계의 버전이 앞으로 같이 변경되리라는 예상을 할 수 있다. hibernate-annotions와 hibernate-commons-annotions 모두 JBoss에 의해 배포되는 동일한 프로젝트의 컴포넌트들이며, 따라서 새로운 프로젝트 릴리즈가 있다면 이 의존관계 모두 바뀔 것이다. 중복의 세번째이자 마지막 패턴은 형제 모듈 의존관계와 형재 모듈 버전에 대한 중복이다. Maven은 부모 POM으로 모든 이러한 중복을 뽑아내도록 하는 간단한 메커니즘을 제공한다.
프로젝트의 소스 코드와 같이 POM에서 중복이 발생한 경우, 앞으로의 문제에 대해 약간은 문을 열어놓는다. 중복된 의존관계 선언은 큰 프로젝트 간의 일관된 버전을 보장하기 어려워진다. 두개나 세개의 모듈만이 있다면, 이것이 주요 문제는 아닐 수 있지만, 조직이 여러 부서를 가로질러 수백개의 컴포넌트를 관리하는 크고, 다중 모듈의 Maven 빌드를 사용하고 있다면, 의존관계 간의 단 하나의 불일치도 혼란과 혼돈을 야기시킬 수 있다. 프로젝트 계층구조에서 ASM 3단계 깊이라고 하는 바이트코드 처리 패키지에 대한 프로젝트의 의존관계에서 단순한 버전 불일치는 해당 특정 모듈에 의존하는 완전히 다른 개발자 그룹에 의해서 관리되는 웹 어플리케이션에 방해를 놓을 수 있다. 단위 테스트들은 의존관계의 한가지 버전으로 실행되기 때문에 통과될 수 있지만, 번들 (이 경우 WAR)이 다른 버전으로 패키징되는 제품화에서 비참하게 실패할 수 있다. 만일 Hibernate Annotation과 같은 것과, 각각의 반복되고 겹치는 의존관계와 제외를 사용하는 수십개의 프로젝트를 가진다면 누군가가 빌드를 엉망으로 만드는 평균 시간은 매우 짧을 것이다. 메이븐 프로젝트가 점점 복잡해질수록 의존관계 목록은 늘어나게 되며, 상위 POM에 버전과 의존관계 선언을 병합하기를 원할 것이다.
동일 계층 모듈 버전의 중복은 Maven에 의해서 직접적으로 원인이 되지 않고 몇차례 이러한 버그에 의해 고생한 뒤에만 배우게 되는 특별히 어려운 문제를 야기시킬 수 있다. 만일 릴리즈를 수행하는 Maven Release 플러그인을 사용한다면, 모든 이러한 동일 계층 의존관계 버전은 자동으로 변경될 것이며, 따라서 이러한 의존관계에 대한 버전을 관리하는 것은 문제가 아니다. simple-web 의 버전 1.3-SNAPSHOT이 simple-persist의 버전 1.3-SNAPSHOT과 의존관계가 있고, 양쪽 프로젝트의 1.3 버전의 릴리즈를 수행한다면 Maven Release 플러그인은 다중 모듈 프로젝트의 POM을 통한 버전들을 자동으로 바꿀 수 있을 만큼 현명하다. Release 플러그인으로 release를 실행하면 빌드에 있는 모든 버전을 1.4-SNAPSHOT으로 증가시키며, release 플러그인은 레파지토리에 코드 변경을 올려놓는다. 대규모 다중 모듈 프로젝트에 대한 배포가 이보다 더 쉬울 수는 없을 것이다.
문제는 개발자가 POM에 변경을 병합하고 진행 중인 배포에 간섭할 때 발생한다. 개발자는 해당 버전을 이전 배포판으로 무심코 바꾸면서 동일 계층 의존관계에 대한 충돌을 병합하고 간혹 잘못 처리하는 경우가 있다. 의존관계의 연속된 버전이 종종 호환될 수 있기 때문에 개발자가 빌드할 때 나타나지 않으며, 실패된 빌드로 지속 통합(continuous integration) 빌드 시스템에서조차도 발견되지 않을 것이다. trunk가 1.4-SNAPSHOT 버전의 컴포넌트로 가득찬 매우 복잡한 빌드를 가정해보고, 이제 개잘자 A가 컴포넌트 B의 1.3-SNAPSHOT 버전에 의존관계의 프로젝트 계층을 깊게 형성하고 있는 컴포넌트 A를 변경했다고 생각해보자. 대부분의 개발자들이 1.4-SNAPSHOT 버전을 가지고 있다고 하더라도, 컴포넌트 B의 버전 1.3-SNAPSHOT과 1.4-SNAPSHOT이 호환된다면 빌드는 성공한다. 메이븐은 개발자의 로컬 레파지토리의 컴포넌트 B에 대한 1.3-SNAPSHOT을 사용하여 프로젝트를 계속해서 빌드한다. 모든 것이 매우 자연스럽게 진행되는 것처럼 보인다. – 프로젝트는 빌드되고, 지속 통합 빌드는 잘 돌아가고… 어떤 사람이 컴포넌트 B와 관련된 당혹스런 버그를 발견했지만, 유해한 보이지 않는 말썽꾸러기로 이를 표시하고 계속 진행한다. 반면에 반응실에서의 펌프는 무엇인가가 터질때까지 꾸준히 압력을 넣고 있다.
‘경솔한’ 이라고 하는 누군가가 A 컴포넌트에서 병합에 대한 충돌이 생겨서 실수로 A 컴포넌트에서 B 컴포넌트로의 의존관계를 1.3-SNAPSHOT으로 바꿨다고 하자. 하지만, 프로젝트의 나머지는 계속해서 진행된다. 바로 이 시간에 다른 개발자들이 B 컴포넌트의 버그를 수정하려고 했었고, 이들은 제품화에서 왜 버그가 수정되지 않은 것처럼 되었는지에 대해 어리둥절했었다. 결국, 누군가가 A 컴포넌트를 살펴보고 의존관계가 잘못된 버전을 가리키고 있다는 것을 알아차렸다. 다행히, 버그는 비용이나 프로젝트를 망칠 정도로 그리 심각하지 않았지만, ‘경솔한’ 씨는 자신이 바보같다고 느끼고 사람들은 전체 의존관계를 망거뜨리기 이전에 믿었던 것보다도 덜 신뢰하게 된다. (원칙적으로 ‘경솔한’ 씨는 이는 사용자 에러이며 Maven의 잘못이 아니라고 생각하겠지만, 자신의 감정을 풀기 위해서 Maven에 대한 끝없는 불평과 불만을 블로그에 담을 가능성이 있다.)
다행스럽게도 의존성 중복과 불일치는 작은 변화를 가했다면 쉽게 방지할 수 있다. 첫번째 할 일은 하나의 프로젝트 이상에서 사용된 모든 의존관계를 찾고 이를 상위 POM의 <dependencyManagement> 부분으로 옮기는 것이다. 이제는 이 프로젝트의 의존관계는 신경쓸 필요가 없다. simple-parent 의 POM이 다음과 같은 내용을 포함한다.

<project>
  ...
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring</artifactId>
        <version>2.0.7</version>
      </dependency>
      <dependency>
        <groupId>org.apache.velocity</groupId>
        <artifactId>velocity</artifactId>
        <version>1.5</version>
      </dependency> 
      <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-annotations</artifactId>
        <version>3.3.0.ga</version>
      </dependency>
      <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-commons-annotations</artifactId>
        <version>3.3.0.ga</version>
      </dependency>
      <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate</artifactId>
        <version>3.2.5.ga</version>
        <exclusions>
          <exclusion>
            <groupId>javax.transaction</groupId>
            <artifactId>jta</artifactId>
          </exclusion>
        </exclusions>
      </dependency>
    </dependencies>
  </dependencyManagement>
  ...
</project>

일단 위의 내용을 모두 옮겼으면, 각각의 POM에서 위의 dependency에 대한 버전을 제거할 필요가 있다. 혹은, 부모 프로젝트에 정의된 <dependencyManagement>를 오버라이드해야 한다. 다음의 간단해진 simple-model을 살펴보자.

<project>
  ...
  <dependencies>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-annotations</artifactId>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate</artifactId>
    </dependency>
  </dependencies>
  ...
</project>


그 다음으로 할 일은 the hibernate-annotations과 hibernate-commons-annotations 버전의 복사를 수정하는 것인데, 이러한 버전들이 일치되어야 하기 대문이다. hibernate-annotations-version 라고 하는 속성을 만들어서 이를 일치시킬 수 있다. simple-parent 부분이 다음과 같이 변경되었다.

<project>
  ...
  <properties>
    <hibernate.annotations.version>3.3.0.ga</hibernate.annotations.version>
  </properties>

  <dependencyManagement>
    ...
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-annotations</artifactId>
      <version>${hibernate.annotations.version}</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-commons-annotations</artifactId>
      <version>${hibernate.annotations.version}</version>
    </dependency>
    ...
  </dependencyManagement>
  ...
</project>


마지막으로 해결할 문제는 동일 수준의 dependency이다. 사용할 수 있는 한가지 방법은 다른 것들과 마찬가지로 해당 dependency들을 <dependencyManagement> 부분으로 모두 옮기고, 최상위 부모 프로젝트에 동일 수준의 프로젝트의 버전을 정의하는 것이다. 분명 이는 가능한 방법이지만, 두개의 내부 정의 속성인 ${project.groupId}와 ${project.version}를 사용해서 버전 문제를 해결할 수도 있다. 이들은 동일 수준의 dependency이기 때문에 상위에 이들을 열거하여 해당 값을 얻어오지 않는다. 따라서 내부에 정의된 ${project.version} 속성을 사용할 것이다. 이들은 동일한 group을 공유하기 때문에 내부에 정의된 ${project.groupId} 속성을 사용해서 현재 POM의 group을 참조하는 해당 선언을 사용하여 앞으로 바뀌는 부분에 대해 방지할 수 있다. simple-command 의 dependency 부분은 다음과 같이 변경된다.

<project>
  ...
  <dependencies>
    ...
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>simple-weather</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>simple-persist</artifactId>
      <version>${project.version}</version>
    </dependency>
    ...
  </dependencies>
  ...
</project>

다음은 dependency의 중복을 방지하는데 방금 언급했던 두가지 최적화에 대한 요약이다.

공통 dependency는 <dependencyManagement>로 옮긴다.

만일 하나 이상의 프로젝트가 특정 dependency를 참조하면, <dependencyManagement>에 dependency 목록을 추가할 수 있다. 상위 POM은 제외시킬 버전과 exclusion들을 포함할 수 있으며, 이 dependency를 참조할 필요가 있는 모든 자식 POM은 groupId와 artifactId를 사용한다. 하위 프로젝트들은 dependency가 <dependencyManagement> 목록에 있으면 버전과 exclusion을 생략할 수 있다.

동일 수주누의 프로젝트에 대해서 내부 정의 프로젝트 버전과 groupId를 사용.

동일 수준의 프로젝트를 참조하는 경우 ${project.version}와 ${project.groupId}를 사용한다. 동일 수준의 프로젝트들은 거의 항상 동일한 groupId를 사용하며, 동일한 릴리즈 버전을 거의 항상 공유한다. ${project.version}을 사용함으로써 이전에 언급되었던 동일 수준의 버전 불일치 문제를 피하게 된다.

8.4 플러그인 최적화
다양한 플러그인 설정을 보면, 몇몇 군데에서 HSQLDB 에 대한 dependency가 겹치게 되는 것을 볼 수 있다. 불행히도, <dependencyManagement>는 플러그인 dependency에 적용하지 못하지만, 버전을 합치기 위한 속성을 여전히 사용할 수 있다. 대부분의 복잡한 Maven 복합 모듈 프로젝트에서는 최상위 POM에 모든 버전을 정의하는 경향이 있다. 이 최상위 POM은 전체 프로젝트에 영향을 미치는 변경에 대한 진원 지점이 된다. 버전 번호를 자바 클래스에서의 스트링 문자라고 생각해보라. 만일 계속해서 문자를 반복하고 있다면, 해당 문자를 변수로 바꾸기를 원할 것이며 그렇게 함으로써 변경할 필요가 있을 경우, 한 장소에서만 이를 변경해야 한다. 최상위 POM에 있는 HSQLDB의 버전을 속성으로 바꾸게 되면 다음과 같은 properties 요소가 만들어진다.

<project>
  ...
  <properties>
    <hibernate.annotations.version>3.3.0.ga</hibernate.annotations.version>
    <hsqldb.version>1.8.0.7</hsqldb.version>
  </properties>
  ...
</project>

그 다음으로 주목할 것은 hibernate3-maven-plugin 설정이 simple-webapp와 simple-command 모듈에 중복되었다는 것이다. 최상위 POM의 dependency를 <dependencyManagement> 부분에서 관리했던 것과 마찬가지로 최상위 POM의 플러그인 설정을 관리할 수 있다. 이와 같이 하려면, 최상위 POM의 build 요소에 <pluginManagement> 요소를 사용한다.

<project>
  ...
  <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>
        <plugin>
          <groupId>org.codehaus.mojo</groupId>
          <artifactId>hibernate3-maven-plugin</artifactId>
          <version>2.1</version>
          <configuration>
            <components>
              <component>
                <name>hbm2ddl</name>
                <implementation>annotationconfiguration</implementation>
              </component>
            </components>
          </configuration>
          <dependencies>
            <dependency>
              <groupId>hsqldb</groupId>
              <artifactId>hsqldb</artifactId>
              <version>${hsqldb.version}</version>
            </dependency>
          </dependencies>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
  ...
</project>

8.5 메이븐 Dependency 플러그인으로 최적화
더 큰 프로젝트에서 dependency의 추가는 POM에 dependency의 수가 늘어감에 따라 종종 깨지는 경향이 있다. dependency가 변경함에 따라 사용하지 않는 dependency를 그대로 나두게 되는 경우가 있으며, 필요한 라이브러리를 위해 명시적인 dependency 선언을 잊는 경우들이 종종 있다. Maven 2.x는 compile 영역으로 연결된 dependency를 포함하고 있기 때문에 컴파일은 문제없이 수행할 수 있겠지만 제품화에서 실행시 문제가 발생될 수 있다. 프로젝트가 Jakarta Commons BeanUtils와 같이 널리 사용되는 프로젝트의 클래스들을 사용하는 경우를 생각해보자. BeanUtils에 대한 명시적인 dependency를 선언하는 대신에 프로젝트는 연결된 dependency로 BeanUtils를 참조하는 Hibernate와 같은 프로젝트를 단순히 의존한다고 생각해보자. 프로젝트는 컴파일을 성공적으로 수행하고 실행도 잘되지만, BeanUtils를 사용하지 않는 Hibernate의 새로운 버전으로 변경할 경우, 컴파일을 다시 수행할 것이고, 실행시 에러가 발생하며, 왜 프로젝트가 컴파일을 멈추게 되었는지 즉시 명확하게 밝힐 수 없을 것이다. 또한, 명시적인 목록으로 dependency 버전을 가지고 있지 않기 때문에 메이븐은 발생될 수 있는 어떠한 버전 충돌도 해결할 수 없다.
메이븐에서 사용하는 가장 최선의 방법은 코드에서 참조되는 클래스에 대해서 항상 명시적인 dependency를 선언하는 것이다. Commons BeanUtils 클래스들을 사용하려고 하면, Commons BeanUtils에 대한 직접적인 dependency를 선언해야 한다. 대행히도, 바이트코드 분석을 통해 메이븐 Dependency 플러그인은 dependency에 대한 직접적인 참조를 밝혀냄으로써 개발자를 도울 수 있다. 이전에 최적화된 변경된 POM을 사용해서 어떠한 에러가 발생되는지를 다음과 같이 살펴보자.

$ mvn dependency:analyze
[INFO] Scanning for projects...
[INFO] Reactor build order:
[INFO]   Chapter 8 Simple Parent Project
[INFO]   Chapter 8 Simple Object Model
[INFO]   Chapter 8 Simple Weather API
[INFO]   Chapter 8 Simple Persistence API
[INFO]   Chapter 8 Simple Command Line Tool
[INFO]   Chapter 8 Simple Web Application
[INFO]   Chapter 8 Parent Project
[INFO] Searching repository for plugin with prefix: 'dependency'.
...
[INFO] ------------------------------------------------------------------------
[INFO] Building Chapter 8 Simple Object Model
[INFO]    task-segment: [dependency:analyze]
[INFO] ------------------------------------------------------------------------
[INFO] Preparing dependency:analyze
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] Nothing to compile - all classes are up to date
[INFO] [resources:testResources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:testCompile]
[INFO] Nothing to compile - all classes are up to date
[INFO] [dependency:analyze]
[WARNING] Used undeclared dependencies found:
[WARNING]    javax.persistence:persistence-api:jar:1.0:compile
[WARNING] Unused declared dependencies found:
[WARNING]    org.hibernate:hibernate-annotations:jar:3.3.0.ga:compile
[WARNING]    org.hibernate:hibernate:jar:3.2.5.ga:compile
[WARNING]    junit:junit:jar:3.8.1:test
...
[INFO] ------------------------------------------------------------------------
[INFO] Building Chapter 8 Simple Web Application
[INFO]    task-segment: [dependency:analyze]
[INFO] ------------------------------------------------------------------------
[INFO] Preparing dependency:analyze
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] Nothing to compile - all classes are up to date
[INFO] [resources:testResources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:testCompile]
[INFO] No sources to compile
[INFO] [dependency:analyze]
[WARNING] Used undeclared dependencies found:
[WARNING]    org.sonatype.mavenbook.ch08:simple-model:jar:1.0:compile
[WARNING] Unused declared dependencies found:
[WARNING]    org.apache.velocity:velocity:jar:1.5:compile
[WARNING]    javax.servlet:jstl:jar:1.1.2:compile
[WARNING]    taglibs:standard:jar:1.1.2:compile
[WARNING]    junit:junit:jar:3.8.1:test


방금 위에 나타난 일부 출력 화면에서 dependency:analyze goal의 출력 화면을 볼 수 있다. 이 goal은 프로젝트에서 간접적인 dependency가 있는지 혹은 참조는 되지만 직접적으로 선언되지 않은 dependency가 있는지를 분석한다. simple-model 프로젝트에서 Dependency 플러그인은 javax.persistence:persistence-api에 대해서 “사용하고 있는 선언되지 않은(used undeclared) dependency”로 나타내고 있다. 자세한 사항을 살펴보기 위해 simple-model 디렉토리로 가서 프로젝트의 모든 직접과 연결 dependency를 보여주는 dependency:tree goal을 실행한다.

$ mvn dependency:tree
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'dependency'.
[INFO] ------------------------------------------------------------------------
[INFO] Building Chapter 8 Simple Object Model
[INFO]    task-segment: [dependency:tree]
[INFO] ------------------------------------------------------------------------
[INFO] [dependency:tree]
[INFO] org.sonatype.mavenbook.ch08:simple-model:jar:1.0
[INFO] +- org.hibernate:hibernate-annotations:jar:3.3.0.ga:compile
[INFO] |  \- javax.persistence:persistence-api:jar:1.0:compile
[INFO] +- org.hibernate:hibernate:jar:3.2.5.ga:compile
[INFO] |  +- net.sf.ehcache:ehcache:jar:1.2.3:compile
[INFO] |  +- commons-logging:commons-logging:jar:1.0.4:compile
[INFO] |  +- asm:asm-attrs:jar:1.5.3:compile
[INFO] |  +- dom4j:dom4j:jar:1.6.1:compile
[INFO] |  +- antlr:antlr:jar:2.7.6:compile
[INFO] |  +- cglib:cglib:jar:2.1_3:compile
[INFO] |  +- asm:asm:jar:1.5.3:compile
[INFO] |  \- commons-collections:commons-collections:jar:2.1.1:compile
[INFO] \- junit:junit:jar:3.8.1:test
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------

위의 결과 화면에서 persistence-api dependency는 hibernate에서 왔다는 것을 볼 수 있다. 이 모듈의 소스에 대한 대강의 스캔을 통해 javax.persistence를 직접적으로 참조하는 수많은 import 구문이 드러났다. 간단한 해결책은 dependency에 직접 참조를 추가하는 것이다. 위의 예제에서 Hibernate와 연결되고, Hibernate 버전이 선언되어 있는 simple-parent의 <dependencyManagement> 부분에 dependency 버전을 추가했다. 마침내 프로젝트의 Hibernate 버전을 변경하기를 원할 것이다. Hibernate dependency 버전 근처에 persistence-api dependency 버전을 위치시키는 것은 이후에 팀이 Hibernate 버전을 변경하기 위해 상위 POM을 수정할 때 더 명확하게 한다.
simple-web 모들의 dependency:analyze 결과 화면을 본다면 simple-model dependency를 직접적으로 참조하는 것을 추가할 필요가 있다는 것을 알 수 있다. simple-webapp의 코드는 직접적으로 simple-model의 모델 객체를 참조하며, simple-model은 simple-persist를 통한 연결 dependency로 simple-webapp에 노출된다. 이는 version과 groupId 모두 공유하는 동일 수준의 dependency이기 때문에 dependency는 ${project.groupId}와 ${project.version}을 사용하는 simple-webapp의 pom.xml 에 정의될 수 있다.
메이븐 Dependency 플러그인은 어떻게 이러한 문제를 찾아냈는가? dependency:analyze는 어떤 클래스와 dependency가 프로젝트의 바이트코드에 의해 직접적으로 참조하는지를 어떻게 아는가? Dependnecy 플러그인은 바이트코드를 분석하는 ObjectWeb 의 ASM(http://asm.objectweb.org/) 을 사용한다. Dependency 플러그인은 현재 프로젝트의 모든 클래스를 살피기 위해 ASM을 사용하고, 참조되는 모든 다른 클래스의 목록을 만든다. 그 다음에 모든 dependency, 직간접 dependency를 점검하고, 직접적인 dependency에 나타난 클래스를 체크한다. 직접적인 dependency에 있지 않은 클래스들은 연결 dependency에서 발견되며, “사용되고 선언되지 않은(used, undeclared) dependency” 의 목록을 만들어 낸다.
반면에 사용되지 않고 선언된 dependency 목록은 점검하기가 다소 더 까다롭고, “사용되고 선언되지 않은 dependency” 목록보다 덜 유용하다. 한가지 이유는 몇몇 dependency들은 실행시나 테스트에서만 사용되며, 바이트코드에서는 찾지 못한다. 결과 화면에서 이들을 보았을 때 매우 명확하다. 예를 들어, JUnit은 이 목록에 나타나지만, 단위테스트에서만 사용되기 때문에 이는 예상 가능하다. 또한 Velocity와 Servlet API dependency가 simple-web 모듈의 이 목록에서 나타나는 것을 볼 것이다. 이는 프로젝트가 이 dependency의 클래스에 직접적인 참조를 가지고 있지 않다고 하더라도 실행시에 여전히 중요하기 때문에 역시 예상 가능하다.
매우 좋은 테스트 커버리지를 가지지 못한다면 사용되지 않고 선언된 dependency를 없앨 때 유의하라. 그렇지 않으면 실행시 에러가 발생할 수 있다. 더 불길한 문제가 바이트코드 최적화와 같이 생기게 된다. 예를 들어, 컴파일러가 상수 값을 대체하고 참조로 최적화하는 것은 당연하다. 이 dependency를 없애는 것은 컴파일 실패를 일으킬 수 있지만, 도구에서는 사용되지 않는 것으로 보여진다. 메이븐 Dependency 플러그인의 앞으로의 버전은 이러한 유형의 문제를 찾아내고 무시하는 데에 더 좋은 기법을 제공할 것이다.
프로젝틍 이러한 공통적인 에러를 발견하는데 주기적으로 dependency:analyze를 사용해야 한다. 특정 조건이 만족하면 빌드를 실패하도록 설정할 수 있으며, 레포트로써 사용이 가능하다.

8.6 최종 POM
전체적으로, 최종 POM파일이 이 장에 대한 참조로 나와있다. 예제 8-1은 simple-parent에 대한 최상위 POM을 보여준다.

예제 8-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.ch08</groupId>
  <artifactId>simple-parent</artifactId>
  <packaging>pom</packaging>
  <version>1.0</version>
  <name>Chapter 8 Simple Parent Project</name>
 
  <modules>
    <module>simple-command</module>
    <module>simple-model</module>
    <module>simple-weather</module>
    <module>simple-persist</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>
        <plugin>
          <groupId>org.codehaus.mojo</groupId>
          <artifactId>hibernate3-maven-plugin</artifactId>
          <version>2.1</version>
          <configuration>
            <components>
              <component>
                <name>hbm2ddl</name>
                <implementation>annotationconfiguration</implementation>
              </component>
            </components>
          </configuration>
          <dependencies>
            <dependency>
              <groupId>hsqldb</groupId>
              <artifactId>hsqldb</artifactId>
              <version>${hsqldb.version}</version>
            </dependency>
          </dependencies>
        </plugin>
      </plugins>
   </pluginManagement>
  </build>
  <properties>
    <hibernate.annotations.version>3.3.0.ga</hibernate.annotations.version>
    <hsqldb.version>1.8.0.7</hsqldb.version>
  </properties>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring</artifactId>
        <version>2.0.7</version>
      </dependency>
      <dependency>
        <groupId>org.apache.velocity</groupId>
        <artifactId>velocity</artifactId>
        <version>1.5</version>
      </dependency> 
      <dependency>
        <groupId>javax.persistence</groupId>
        <artifactId>persistence-api</artifactId>
        <version>1.0</version>
      </dependency>
      <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-annotations</artifactId>
        <version>${hibernate.annotations.version}</version>
      </dependency>
      <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-commons-annotations</artifactId>
        <version>${hibernate.annotations.version}</version>
      </dependency>
      <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate</artifactId>
        <version>3.2.5.ga</version>
        <exclusions>
          <exclusion>
            <groupId>javax.transaction</groupId>
            <artifactId>jta</artifactId>
          </exclusion>
        </exclusions>
      </dependency>
    </dependencies>
  </dependencyManagement>
 
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>


예제 8-2의 POM은 도구의 명령행 버전인 simple-command 에 대한 POM이다.

예제 8-2. simple-command에 대한 최종 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.ch08</groupId>
    <artifactId>simple-parent</artifactId>
    <version>1.0</version>
  </parent>
  <artifactId>simple-command</artifactId>
  <packaging>jar</packaging>
  <name>Chapter 8 Simple Command Line Tool</name>
  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-jar-plugin</artifactId>
          <configuration>
            <archive>
              <manifest>
                <mainClass>org.sonatype.mavenbook.weather.Main</mainClass>
                <addClasspath>true</addClasspath>
              </manifest>
            </archive>
          </configuration>
        </plugin>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-surefire-plugin</artifactId>
          <configuration>
            <testFailureIgnore>true</testFailureIgnore>
          </configuration>
        </plugin>
        <plugin>
         <artifactId>maven-assembly-plugin</artifactId>
          <configuration>
            <descriptorRefs>
              <descriptorRef>jar-with-dependencies</descriptorRef>
            </descriptorRefs>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
  <dependencies>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>simple-weather</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>simple-persist</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.velocity</groupId>
      <artifactId>velocity</artifactId>
    </dependency>   
  </dependencies>
</project>


예제 8-3은 simple-model 프로젝트의 POM이다. simple-model 프로젝트는 애플리케이션을 통해 사용되는 모든 모델 객체를 포함한다.

예제 8-3. simple-model에 대한 최종 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.ch08</groupId>
    <artifactId>simple-parent</artifactId>
    <version>1.0</version>
  </parent>
  <artifactId>simple-model</artifactId>
  <packaging>jar</packaging>
  <name>Chapter 8 Simple Object Model</name>
  <dependencies>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-annotations</artifactId>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate</artifactId>
    </dependency>
    <dependency>
      <groupId>javax.persistence</groupId>
      <artifactId>persistence-api</artifactId>
    </dependency>
  </dependencies>
</project>



예제 8-4는 simple-persist 프로젝트의 POM이다. simple-persist 프로젝트는 Hibernate를 사용해서 구현된 모든 저장 로직을 포함한다.

예제 8-4. simple-persist에 대한 최종 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.ch08</groupId>
    <artifactId>simple-parent</artifactId>
    <version>1.0</version>
  </parent>
  <artifactId>simple-persist</artifactId>
  <packaging>jar</packaging>
  <name>Chapter 8 Simple Persistence API</name>
  <dependencies>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>simple-model</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate</artifactId>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-annotations</artifactId>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-commons-annotations</artifactId>
    </dependency>
    <dependency>
      <groupId>org.apache.geronimo.specs</groupId>
      <artifactId>geronimo-jta_1.1_spec</artifactId>
      <version>1.1</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring</artifactId>
    </dependency>
  </dependencies>
</project>


예제 8-5는 simple-weather 프로젝트의 POM이다. simple-weather 프로젝트는 Yahoo! Weather RSS 피드를 파싱하는 모든 로직을 포함하는 프로젝트이다. 이 프로젝트는 simple-model 프로젝트를 의존하고 있다.

예제 8-5. 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.ch08</groupId>
    <artifactId>simple-parent</artifactId>
    <version>1.0</version>
  </parent>
  <artifactId>simple-weather</artifactId>
  <packaging>jar</packaging>
  <name>Chapter 8 Simple Weather API</name>
  <dependencies>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>simple-model</artifactId>
      <version>${project.version}</version>
    </dependency>
    <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>org.apache.commons</groupId>
      <artifactId>commons-io</artifactId>
      <version>1.3.2</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>


마지막으로 예제 8-6은 simple-webapp 프로젝트의 POM이다. simple-webapp 프로젝트는 HSQLDB 에서 조회한 날씨 예측을 저장하고 또한 simple-weather 프로젝트에 의해 생성된 라이브러리와 상호작용하는 웹 애플리케이션을 포함한다.

예제 8-6. 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.ch08</groupId>
    <artifactId>simple-parent</artifactId>
    <version>1.0</version>
  </parent>
  <artifactId>simple-webapp</artifactId>
  <packaging>war</packaging>
  <name>Chapter 8 Simple Web Application</name>
  <dependencies>
    <dependency>
      <groupId>org.apache.geronimo.specs</groupId>
      <artifactId>geronimo-servlet_2.4_spec</artifactId>
      <version>1.1.1</version>
    </dependency>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>simple-model</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>simple-weather</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>simple-persist</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring</artifactId>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>jstl</artifactId>
      <version>1.1.2</version>
    </dependency>
    <dependency>
      <groupId>taglibs</groupId>
      <artifactId>standard</artifactId>
      <version>1.1.2</version>
    </dependency>
    <dependency>
      <groupId>org.apache.velocity</groupId>
      <artifactId>velocity</artifactId>
    </dependency>
  </dependencies>
  <build>
    <finalName>simple-webapp</finalName>
    <plugins>
      <plugin>
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>maven-jetty-plugin</artifactId>
        <version>6.1.9</version>
        <dependencies>
          <dependency>
            <groupId>hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <version>${hsqldb.version}</version>
          </dependency>
        </dependencies>
      </plugin>
    </plugins>
  </build>
</project>


8.7 결론
이 장은 빌드의 쉬운 유지보수를 위해 dependency와 플러그인에 대한 통제를 향상시키는 몇가지 기법을 소개했다. 이러한 방식을 통해 중복과 그로 인한 잠재적인 문제를 최소화시키기 위해 빌드를 지속적으로 검토하기를 권한다. 프로젝트가 진행됨에 따라 새로운 dependency가 불가피하게 추가되며, 한군데에서 이전에 사용되는 dependency가 이제 10군데에서 사용되는 것을 발견할 것이며 이는 상위로 옮겨져야 한다. 사용되고 사용되지 않는 dependency 목록은 시간이 지남에 따라 변경되며 메이븐 Dependency 플러그인을 사용해서 쉽게 깔끔하게 처리될 수 있다.


<<Pre

저작자 표시 비영리 동일 조건 변경 허락
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
7.1. 소개

이 장에서 5장과 6장의 예제를 야후 날씨 피드로부터 데이터를 읽는 간단한 웹 어플리케이션과 명령행 유틸리티로 만들기 위해 Spring 프레임워크와 Hibernate를 사용하는 프로젝트로 발전시키는 다중 모듈 프로젝트를 생성한다. 4장에서 만든 simple-weather 코드는 5장에서 정의된 simple-webapp 프로젝트로 합해진다. 이러한 다중 모듈 프로젝트를 생성하는 과정에서 메이븐을 살펴보고 재사용을 위한 모듈 프로젝트를 생성하는데 사용될 수 있는 서로 다른 방법을 설명할 것이다.

7.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의 책의 예제 코드와 같이 다운로드받을 수 있다. 디렉토리에 이 파일을 압축을 풀고, 이 장에서 만들 다중 모듈 메이븐 프로젝트로 이동하라. simple-parent/ 프로젝트 디렉토리에서 pom.xml 와 5개의 하위 모듈 디렉토리인 simple-model/, simple-persist/, simple-command/, simple-weather/, simple-webapp/ 를 볼 수 있을 것이다. 웹 브라우저에서 예제코드를 보기 원한다면, http://www.sonatype.com/book/examples-1.0 로 이동해서, ch07/ 디렉토리를 클릭하라.

7.1.2. 다중 모듈 엔터프라이즈 프로젝트

대형 엔터프라이즈 수준의 프로젝트에 대한 복잡성을 제시하는 것은 이 책의 범위를 훨씬 넘어서는 것이다. 그러한 프로젝트들은 다중 DB와, 외부 시스템과의 통합, 부서로 구획화되는 하위 프로젝트들로 규정된다. 이러한 프로젝트들은 통상 수천 라인의 코드로 구성되며 수십 혹은 수백명의 개발자 공수와 관련된다. 그러한 완전한 예제가 이 책의 범위 밖의 것이라도, 대형 엔터프라이즈 어플리케이션의 복잡성을 제시하는 동일한 프로젝트를 제공할 수 있다. 결론적으로, 이 장에서 설명된 것 이상의 모듈화에 대한 어떤 가능성을 제시한다.
이 장에서 두개의 어플리케이션인 야후 날시 피드에 대한 명령행 질의 도구와, 야후 날씨 피드를 검색하는 웹 어플리케이션을 만드는 다중 모듈 메이븐 프로젝트를 살펴볼 것이다. 이 두개의 어플리케이션은 내장된 DB에 질의 결과를 저장한다. 각각은 사용자가 내장된 DB로부터 이전 날시 데이터를 조회하는 것을 가능하게 한다. 두개의 어플리케이션은 어플리케이션 로직을 재사용하며 저장 라이브러리를 공유한다. 이 장의 예제는 4장에서 소개한 야후 날씨 파싱 코드에 대해 빌드한다. 이 프로젝트는 그림 7-1에 나타난 바와 같이 5개의 하위 모듈로 나뉜다.

그림 7-1. 다중 모듈 엔터프라이즈 어플리케이션의 모듈 관계

그림 7-1에서 simple-parent의 5가지 하위 모듈이 있음을 볼 수 있을 것이다.

simple-model
이 모듈은 야후 날씨 피드로부터 반환되는 객체를 모델링한 간단한 객체 모델을 정의한다. 이 객체 모델은 Weather, Condition, Atmosphere, Location, Wind 객체를 포함한다. 어플리케이션이 야후 날씨 피드를 파싱할 때, simple-weather 에 정의된 파서는 XML을 파싱하고 어플리케이션에 의해서 사용될 Weather 객체를 생성한다. 이 프로젝트는 각각의 모델 객체를 관계형 DB의 해당 테이블로 매핑하기 위해 simple-persist 의 로직에 의해서 사용되는 Hibernate 3 어노테이션으로 표기된 모델 객체를 포함한다.

simple-weather
이 모듈은 야후 날씨 피드로부터 데이터를 읽어서 결과 XML로 파싱하는데 필요한 모든 로직을 포함한다. 이 피드로부터 반환되는 XML은 simple-model 에 정의되는 모델 객체로 변환된다. simple-weather는 simple-model에 대해 의존관계를 가진다. simple-weather는 simple-command와 simple-webapp 프로젝트 모두에 의해 참조되는 WeatherService 객체를 정의한다.

simple-persist
이 모듈은 내장 DB에 Weather 객체를 저장하는데 설정되는 DAO(Data Access Object)를 포함한다. 이 다중 모듈 프로젝트에서 정의된 두개의 어플리케이션은 내장 DB에 데이터를 저장하기 위해 simple-persist에 정의된 DAO를 사용하게 된다. 이 프로젝트에서 정의된 DAO는 simple-model 에서 정의된 모델 객체를 이해하고 반환한다. simple-persist는 simple-model에 대해 직접적으로 연관관계를 맺으며, 모델 객체에 표현된 Hibernate 어노테이션에 대해 의존관계를 가진다.

simple-webapp
웹 어플리케이션 프로젝트는 simple-weather 에서 정의된 WeatherService 와 simple-persist에서 정의된 DAO를 사용하는 두개의 Spring MVC Controller 구현체를 포함한다. simple-webapp는 simple-weather와 simple-persist에 대해 직접적으로 의존관계를 가지며, simple-model 에 대해 연결 의존관계를 가진다.

simple-command
이 모듈은 야후 날씨 피드를 조회하는데 사용될 수 있는 간단한 명령행 도구를 포함한다. 이 프로젝트는 정적 main() 함수를 가지는 클래스를 포함하며 simple-weather에서 정의된 WeatherService와 simple-persist에서 정의된 DAO와 상호작요한다. simple-command는 simple-weather와 simple-persist에 대해서 직접적인 의존관계를 가지며, simple-model에 대해 연결 의존관계를 가진다.

이 장은 책에서 소개하기에 간단한 고안된 예제를 포함하지만, 5개의 하위모듈을 정의하는데는 충분히 복잡하다. 고안된 예제는 5개의 클래스와 두개의 서비스 클래스를 가진 저장 라이브러리, 5~6개의 클래스를 갖는 날씨 파싱 라이브러리를 가지는 모델 프로젝트를 포함하지만, 실제 세계의 시스템은 수백개의 객체와, 수십개의 저장 라이브러리들, 여러 부서로 확장된 서비스 라이브러리들을 포함하는 모델 프로젝트를 가질 수도 있다. 이 예제에서 포함된 코드는 짧은 시간 동안 이해하는데 충분히 직관적이라고 인정하지만, 일부러 모듈화 프로젝트에 대한 빌드를 수행했다. 이 장의 예제를 살펴보려고 하고 현재의 모델 프로젝트가 단지 5개의 클래스만을 가지고 있다는 사실에 Maven이 너무나도 복잡한 것을 조성한다는 생각을 쉽게 생각할 수도 있다. Maven 사용이 특정 수준의 모듈화를 제시하고 있지만, Maven의 다중 모듈 기능을 소개하려는 목적으로 단순한 예제 프로젝트를 고의로 복잡하게 구성했음을 이해하라.

7.1.3. 예제에서 사용된 기술

이 장의 예제는 유명하지만 메이븐과 직접적으로 관계가 없는 몇가지 기술과 관련된다. 이러한 기술들에는 Spring 프레임워크와 Hibernate가 있다. Spring 프레임워크는 제어 역행(IoC) 컨테이너로 다양한 J2EE 라이브러리와의 상호작용을 단순화시키려는 목적을 가진 여러 프레임워크로 구성된다. 어플리케이션 개발에 기반 프레임워크로 Spring 프레임워크를 사용한다는 것은 Hibernate나 iBATIS와 같은 저장 프레임워크나 혹은 JDBC, JNDI, JMS와 같은 엔터프라이즈 API를 사용해서 처리하는 업무에 대한 여러 업무 처리를 수행할 수 있는 수많은 도움이 되는 추상화에 대한 접근을 제공하는 것이다. Spring 프레임워크는 Sun Microsystems의 중량 엔터프라이즈 표준에 대한 대체로 과거 수년 동안 인기를 누리고 있다. Hibernate는 관계형 DB를 자바 객체의 집합체로 보면서 상호작용을 가능하게 하는 널리 사용되는 OR 매핑 프레임워크이다. 이 예제는 어플리케이션에 대해 재사용 가능한 컴포넌트를 노출시키는 Spring 프레임워크를 사용하고 내장 DB에 날씨 데이터를 저장하는데 Hibernate를 사용하는 간단한 웹 어플리케이션과 명령행 어플리케이션 구축에 초점을 둔다.
Maven을 사용할 때 이러한 기술을 사용하여 프로젝트를 어떻게 구성할지를 설명하기 위해 이러한 프레임워크에 대한 참고를 추가하기로 결정했다. 이 장으로 통해 이러한 기술들을 간략하게 소개하고는 있지만, 이러한 기술에 대한 전체적인 설명을 하지는 않을 것이다. Spring 프레임워크에 대한 더 많은 정보는 프로젝트의 웹 사이트인 http://www.springframework.org/ 를 참고하라. Hibernate와 Hibernate 어노테이션에 대한 더 많은 정보에 대해서는 프로젝트의 웹 사이트인 http://www.hibernate.org 를 참고하라. 이 장은 내장 DB로 HSQLDB (Hyper-threaded Structured Query Language Database)를 사용하며, 이 DB에 대한 더 많은 정보는 웹 사이트인 http://hsqldb.org/ 를 참고하라.

7.2. 간단한 부모 프로젝트

simple-parent 프로젝트는 5개의 하위 모듈인 simple-command, simple-model, simple-weather, simple-persist, simple-webapp를 참조하는 pom.xml 를 가진다. 최상위 pom.xml 은 예제 7-1에 나타난다.

예제 7-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.ch07</groupId>
  <artifactId>simple-parent</artifactId>
  <packaging>pom</packaging>
  <version>1.0</version>
  <name>Chapter 7 Simple Parent Project</name>
 
  <modules>
    <module>simple-command</module>
    <module>simple-model</module>
    <module>simple-weather</module>
    <module>simple-persist</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>

위의 부모 POM와 예제 6-1에서 정의한 부모 POM 간의 유사성에 유의하라. 이 두개의 POM 간의 유일한 차이는 하위모듈 목록이다. 이전 예제가 단지 2개의 하위 모듈을 가진 반면에, 위의 부모 POM은 5개의 하위모듈을 가지고 있다. 다음의 절에서는 이들 각각 5개의 하위 모듈에 대해 자세하게 다룰 것이다. 예제에서 자바 어노테이션을 사용하고 있기 때문에 컴파일러의 target을 Java 5 JVM으로 설정했다.

7.3. 간단한 모델 모듈

대부분의 엔터프라이즈 프로젝트가 필요로 하는 첫번째 것은 객체 모델이다. 객체 모델은 어떠한 시스템에서든지 핵심 도메인 객체를 담고 있다. 은행 시스템은 Account, Customer, Transaction 로 구성되는 객체 모델을 가질 것이며, 혹은 스포츠 점수를 담고 상호작용하는 시스템은 Team과 Game 객체를 가질 것이다. 어떠한 것이든, 객체 모델에 해당 시스템에 개념을 모델링하는 좋은 기회가 있다. Maven에서 이러한 프로젝트를 널리 참조되는 별도의 프로젝트로 분리하는 것이 보편적인 기법이다. 이 예제 시스템에서 4개의 다른 객체들을 참조하는 Weather 객체를 가진 야후 날씨 피드에 대한 각각의 질의를 담게된다. 풍향, 체감온도, 풍속이 Wind객체에 저장된다. 우편번호, 도시, 지역, 국가를 포함하는 위치 데이터가 Location 클래스에 저장된다. 습도, 최대 가시거리, 기압, 기압의 높고 낮음의 여부와 같은 대기 조건이 Atmosphere 클래스에 저장된다. 조건에 대한 문자 기술, 온도, 관측자의 데이터가 Condition 클래스에 저장된다. 그림 7-2를 참고하라.

그림 7-2. 날씨 데이터에 대한 간단한 객체 모델

이 간단한 객체 모델에 대한 pom.xml 파일은 어떤 설명을 포함하는 하나의 의존관계를 포함한다. 객체 모델은 Hibernate 어노테이션을 가지고 주해된다. 이러한 어노테이션을 해당 모델을 관계형 DB의 테이블로 매핑하는데 사용한다. 의존관계는 org.hibernate:hibernate-annotations:3.3.0.ga 이다. 예제 7-2에 나타난 pom.xml 을 살펴보고, 이러한 어노테이션에 대해 몇가지 예로 다음 예제들을 볼 수 있을 것이다.

예제 7-2. simple-model의 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>
  <parent>
    <groupId>org.sonatype.mavenbook.ch07</groupId>
    <artifactId>simple-parent</artifactId>
    <version>1.0</version>
  </parent>
  <artifactId>simple-model</artifactId>
  <packaging>jar</packaging>

  <name>Simple Object Model</name>

  <dependencies>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-annotations</artifactId>
      <version>3.3.0.ga</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-commons-annotations</artifactId>
      <version>3.3.0.ga</version>
    </dependency>
  </dependencies>
</project>

src/main/java/org/sonatype/mavenbook/weather/model에서 어노테이션을 포함한 Weather 모델 객체가 있는 Weather.java 를 볼 수 있다. Weather 객체는 단순한 자바빈이다. 이것은 id, location, condition, wind, atmosphere와 같은 private 멤버 변수를 가지고, 데이터들은 속성이 name 이라면, 인자가 없는 public getter 메소드인 getName()을 갖고, 하나의 인자를 갖는 setter로 setName(String name)을 갖는 것처럼 public getter와 setter 메소드들로 노출된다. Id 속성에 대해 getter와 setter 메소드를 보여주지만, 몇몇 값을 저장하는 대부분의 다른 속성에 대한 getter와 setter 대부분은 생략한다. 예제 7-3을 참고하라.

예제 7-3. 어노테이션이 있는 Weather 모델 객체
package org.sonatype.mavenbook.weather.model;

import javax.persistence.*;

import java.util.Date;

@Entity
@NamedQueries({
  @NamedQuery(name="Weather.byLocation",
              query="from Weather w where w.location = :location")
})
public class Weather {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;

    @ManyToOne(cascade=CascadeType.ALL)
    private Location location;

    @OneToOne(mappedBy="weather",cascade=CascadeType.ALL)
    private Condition condition;

    @OneToOne(mappedBy="weather",cascade=CascadeType.ALL)
    private Wind wind;

    @OneToOne(mappedBy="weather",cascade=CascadeType.ALL)
    private Atmosphere atmosphere;

    private Date date;
   
    public Weather() {}

    public Integer getId() { return id; }
    public void setId(Integer id) { this.id = id; }

    // All getter and setter methods omitted...
}

Weather 클래스에서 simple-persist 프로젝트에 대한 가이드를 제공하는 Hibernate 어노테이션을 사용하고 있다. 이러한 어노테이션들은 객체를 관계형 DB의 테이블로 매핑하는데 Hibernate에 의해 사용된다. Hibernate 어노테이션에 대해 전체적으로 설명하는 것은 이 장의 범위를 넘어서는 것이지만, 여기에 간략하게 설명을 하겠다. @Entity 어노테이션은 해당 클래스를 저장 엔티티로 지정한다. 해당 클래스에 대해 @Table 어노테이션을 생략했는데, Hibernate는 클래스의 이름을 테이블의 이름으로 Weather 을 매핑하는데 사용한다. @NamedQueries 어노테이션은 simple-persist의 WeatherDAO가 사용하는 질의를 정의한다. @NamedQuery 어노테이션의 질의 언어는 HQL (Hibernate Query Language)라고 하는 것으로 작성된다. 각각의 멤버 변수는 컬럼 유형과 해당 컬럼에 의해 의미하는 관계로 정의되는 어노테이션으로 표시된다.

Id
id 속서은 @Id 의 어노테이션을 가진다. 이것은 DB 테이블의 PK를 포함하는 속성으로 id 속성을 표기한다. @GeneratedValue는 새로운 PK 값을 어떻게 생성할지를 통제한다. id의 경우, IDENTITY GenerationType 을 사용하고 있으며, 이는 내부 DB의 ID 생성 기능을 사용한다.

Location
각각의 Weather 객체 인스턴스는 Location 객체 대응된다. Location 객체는 우편번호로 표현되며, @ManyToOne은 Weather 객체가 동일한 인스턴스를 참조하는 동일한 Location 객체를 가리킴을 보증한다. @ManyToOne 의 cascade 속성은 Weather 객체를 저장할 때마다 Location 객체가 저장됨을 보장한다.

Condition, Wind, Atmosphere
이들 각각의 객체는 ALL이라는 CascadeType과 같이 @OneToOne으로 매핑된다. 이것은 Weather 객체를 저장할 때마다 Weather 테이블, Condition 테이블, Wind 테이블, Atmosphere 테이블에 해당 행을 입력하게 된다.

Date
Date는 어노테이션이 표기되지 않았다. 이것은 Hibernate가 이러한 매핑을 정의하는 컬럼 기본값에 대한 모든 것을 사용함을 의미한다. 컬럼명은 date 가 되며, 컬럼 유형은 Date객체에 일치하는 적절한 시간이 될 것이다.

유의
테이블 매핑에서 생략하고자 하는 속성에 대해서는 @Transient 어노테이션을 사용해서 해당 속성을 표기할 수 있다.

다음은 예제 7-4에 나타난 두번째 모델 객체들 중의 하나인 Condition 에 대해서 살펴보자. 이 클래스 역시 src/main/java/org/sonatype/mavenbook/weather/model에 위치한다.

예제 7-4. simple-model의 Condition 모델 객체
package org.sonatype.mavenbook.weather.model;

import javax.persistence.*;

@Entity
public class Condition {

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Integer id;

    private String text;
    private String code;
    private String temp;
    private String date;

    @OneToOne(cascade=CascadeType.ALL)
    @JoinColumn(name="weather_id", nullable=false)
    private Weather weather;

    public Condition() {}

    public Integer getId() { return id; }
    public void setId(Integer id) { this.id = id; }

    // All getter and setter methods omitted...
}

Condition 클래스는 Weather 클래스와 유사하다. @Entity 어노테이션으로 표기되며, id 속성에 대해 유사한 어노테이션을 가진다. text, code, temp, date 속성은 모두 기본 컬럼 세팅으로 표기되며, weather 속성은 @OneToOne 어노테이션과 weather_id라는 이름의 FK 컬럼을 가지는 관련 Weather 객체를 참조하는 또 다른 어노테이션으로 표기된다.

7.4. 간단한 날씨 모듈

다음에 설명하려는 모듈은 “서비스”라는 것으로 생각할 수 있다. Simple Weather 모듈은 야후 날씨 RSS 피드로부터 데이터를 읽고 파싱하는데 필요한 모든 로직을 담는 모듈이다. Simple Weather가 세개의 자바 클래스와 하나의 JUnit 테스트를 포함하고 있지만, Simple Web Application과 Simple Command-line Utility 모두에게 단일 컴포넌트인 WeatherService 로 표현될 것이다. 매우 종종 엔터프라이즈 프로젝트는 중요한 비즈니스 로직이나 외부 시스템과 연계되는 로직을 포함하는 몇몇 API 모듈을 포함하게 된다. 은행 시스템은 외부 데이터 제공자로부터 데이터를 읽어서 파싱하는 모듈을 가질 수 있으며, 스포츠 점수를 보여주는 시스템은 농구나 축구에 대한 실시간 점수를 제공하는 XML 피드와 상호작용할 수도 있다. 예제 7-5에서 이 모듈은 야후 날씨와 상호작용에 관련된 네트워크 작업과 XML 파싱의 모든 것을 감추게 된다. 다른 모듈들은 이 모듈에 대해 의존관계를 가지며 인자로 우편번호를 넘기고 Weather 객체를 반환하는 WeatherService의 retrieveForecast() 메소드를 단순히 호출한다.

예제 7-5. 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.ch07</groupId>
    <artifactId>simple-parent</artifactId>
    <version>1.0</version>
  </parent>
  <artifactId>simple-weather</artifactId>
  <packaging>jar</packaging>

  <name>Simple Weather API</name>

  <dependencies>
    <dependency>
      <groupId>org.sonatype.mavenbook.ch07</groupId>
      <artifactId>simple-model</artifactId>
      <version>1.0</version>
    </dependency>
    <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>org.apache.commons</groupId>
      <artifactId>commons-io</artifactId>
      <version>1.3.2</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

simple-weather POM은 simple-parent POM을 상속받고 있으며, jar로 패키징하도록 세팅되며, 다음과 같은 의존관계가 추가된다.

org.sonatype.mavenbook.ch07:simple-model:1.0
simple-weather는 Weather 객체에 야후 날씨 RSS 피드를 파싱한다. simple-model에 대해 직접적인 의존관계를 가진다.

log4j:log4j:1.2.14
simple-weather는 로그 메시지를 출력하는 Log4J 라이브러를 사용한다.

dom4j:dom4j:1.6.1 and jaxen:jaxen:1.1.1
이 의존관계 모두 야후 날씨로부터 반환되는 XML을 파싱하는데 사용된다.

org.apache.commons:commons-io:1.3.2 (scope=test)
이 테스트 영역의 의존관계는 YahooParserTest에 의해 사용된다.

다음은 예제 7-6에 나타난 WeatherService 클래스이다. 이 클래스는 예제 6-3의 WeatherService 클래스와 매우 유사하게 보일 것이다. WeatherService 가 동일하지만, 이 장의 예제에는 약간 다른 것이 있다. 이 장의 retrieveForecast() 메소드는 Weather 객체를 반환하고, 포맷팅은 WeatherService 를 호출하는 어플리케이션에 맡겨둔다. 다른 주요 변화는 YahooRetriever와 YahooParser 가 WeatherService 빈의 두가지 빈 속성이라는 것이다.

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

import java.io.InputStream;

import org.sonatype.mavenbook.weather.model.Weather;

public class WeatherService {

  private YahooRetriever yahooRetriever;
  private YahooParser yahooParser;

  public WeatherService() {}

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

    // Parse DataS
    Weather weather = yahooParser.parse(zip, dataIn);

    return weather;
  }

  public YahooRetriever getYahooRetriever() {
    return yahooRetriever;
  }

  public void setYahooRetriever(YahooRetriever yahooRetriever) {
    this.yahooRetriever = yahooRetriever;
  }

  public YahooParser getYahooParser() {
    return yahooParser;
  }

  public void setYahooParser(YahooParser yahooParser) {
    this.yahooParser = yahooParser;
  }
}

마지막으로 이 프로젝트에서 ApplicationContext 라고 하는 것을 만들기 위해 Spring 프레임워크에 의해 사용되는 XML 파일을 가진다. 먼저 몇가지 설명이 필요한데, 웹 어플리케이션과 명령행 유틸리티 모두는 WeatherService 클래스와 상호작용할 필요가 있으며, 모두 weatherService 라는 이름을 사용해서 Spring의 ApplicationContext 로부터 해당 클래스의 인스턴스를 조회함으로써 그와 같이 수행한다. 웹 어플리케이션은 WeatherService 인스턴스와 관련된 Spring MVC 컨트롤러를 사용하며, 명령행 유틸리티는 정적 main() 함수에 ApplicationContext로부터 WeatherService를 로딩한다. 재사용을 위해 src/main/resources에 applicationContext-weather.xml 파일을 포함시켰으며, 이는 클래스패스에서 사용 가능하다. simple-weather 모듈에 대한 의존관계를 가진 모듈들은 Spring 프레임워크의 ClasspathXmlApplicationContext를 사용해서 해당 어플리케이션 컨텍스트를 로딩할 수 있다. 그 다음에 weatherService로 명명된 WeatherService의 인스턴스를 참조할 수 있다. 예제 7-7을 참고하라.

예제 7-7. simple-weather 모듈에 대한 Spring의 ApplicationContext
<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
             http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
    default-lazy-init="true">

    <bean id="weatherService"
             class="org.sonatype.mavenbook.weather.WeatherService">
      <property name="yahooRetriever" ref="yahooRetriever"/>
      <property name="yahooParser" ref="yahooParser"/>
    </bean>   

    <bean id="yahooRetriever"
             class="org.sonatype.mavenbook.weather.YahooRetriever"/>

    <bean id="yahooParser"
             class="org.sonatype.mavenbook.weather.YahooParser"/>
</beans>

위의 문서는 세가지 빈인 yahooParser, yahooRetriever, weatherService을 정의한다. weatherService 빈은 WeatherService의 인스턴스이며, 이 XML 문서는 해당 클래스의 명명된 인스턴스로의 참조를 가진 yahooParser와 yahooRetriever 속성을 사용한다. 이 applicationContext-weather.xml 파일을 이 다중 모듈 프로젝트의 하위 시스템의 구조를 정의하는 것으로 생각하라. simple-webapp와 simple-command와 같은 프로젝트들은 이 컨텍스트를 참조하고 YahooRetriever와 YahooParser의 인스턴스와 관계를 이미 가지는 WeatherService의 인스턴스를 가지고 온다.

7.5 Simple Persist 모듈

이 모듈은 두개의 매우 단순한 DAO(Data Access Object) 를 정의한다. DAO는 저장 오퍼레이션에 대한 인터페이스를 제공한다. Hibernate와 같은 객체-관계형 매핑(ORM) 프레임워크를 사용하는 어플리케이션에서 DAO는 객체주위에 보통 정의된다. 이 프로젝트에서 WeatherDAO와 LocationDAO의 두개의 DAO의 객체를 정의한다. WeatherDAO 클래스는 Weather 객체를 DB에 저장하고 id의 Weather 객체를 조회하고, 특정 위치와 일치하는 Weather를 조회한다. LocationDAO는 우편번호를 통해 Location 객체를 조회한다. 먼저, 예제 7-8의 simple-persist POM을 살펴보자.

예제 7-8. simple-persist 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.ch07</groupId>
    <artifactId>simple-parent</artifactId>
    <version>1.0</version>
  </parent>
  <artifactId>simple-persist</artifactId>
  <packaging>jar</packaging>

  <name>Simple Persistence API</name>

  <dependencies>
    <dependency>
      <groupId>org.sonatype.mavenbook.ch07</groupId>
      <artifactId>simple-model</artifactId>
      <version>1.0</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate</artifactId>
      <version>3.2.5.ga</version>
      <exclusions>
        <exclusion>
          <groupId>javax.transaction</groupId>
          <artifactId>jta</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-annotations</artifactId>
      <version>3.3.0.ga</version>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-commons-annotations</artifactId>
      <version>3.3.0.ga</version>
    </dependency>
    <dependency>
      <groupId>org.apache.geronimo.specs</groupId>
      <artifactId>geronimo-jta_1.1_spec</artifactId>
      <version>1.1</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring</artifactId>
      <version>2.0.7</version>
    </dependency>
  </dependencies>
</project>

위의 POM 파일은 부모 POM으로 simple-parent를 참조하며, 몇가지 의존관계를 정의한다. simple-persist의 POM에 있는 의존관계들은 다음과 같다.

org.sonatype.mavenbook.ch07:simple-model:1.0
simple-weather 모듈과 같이 이 저장 모듈은 simple-model에 정의된 핵심 모델 객체를 참조한다.

org.hibernate:hibernate:3.2.5.ga
Hibernate 버전 3.2.5.ga에 대한 의존관계를 정의하지만, Hibernate에 대한 의존관계는 제외했음을 유의하라. javax.transaction:javax 의존관계가 공식 Maven 레파지토리에서 사용 가능하지 않기 때문에 이와 같이 했다. 이 의존관계는 Sun의 명세들에 대한 의존관계들이 아직 중앙 Maven 레파지토리에서 사용할 수 있기 때문에 그렇다. 이러한 Sun의 의존관계들에 대한 다운로딩시 메시지에 대한 번거로움을 피하기 위해서 Hibernate에서 이러한 의존관계를 제외하고 다음의 의존관계를 추가한다.

org.apache.geronimo.specs:geronimo-jta_1.1_spec:1.1
서블릿과 JSP API와 같이 Apache Geronimo 프로젝트는 Apache 라이선스 하에서 엔터프라이즈 API의 많은 인증된 버전들이 배포되기에 충분하다. 이는 JDBC, JNDI, JTA API들에 대한 의존관계를 컴포넌트에 추가할 때마다 org.apache.geronimo.specs groupId 밑에 있는 해당 라이브러리를 검색할 수 있음을 의미한다.

org.springframework:spring:2.0.7
이는 의존관계로 전체 Spring 프레임워크를 포함한다.

유의
사용하는 Spring의 컴포넌트만을 의존관계로 갖는 것이 보통 좋은 기법이다. Spring 프레임워크는 spring-hibernate3와 같이 특정 산출물들을 만들어내는데에 매우 효과적이다.

왜 Spring에 대해 의존관계가 있는가? Hibernate 통합에 대해서 생각해볼 때 Spring은 HibernateDaoSupport 와 같은 헬퍼 클래스들을 제공한다. HibernateDaoSupport에서 제공하는 것에 대한 예제로 예제 7-9의 WeatherDAO에 대한 핵심 부분을 살펴보자.

예제 7-9. simple-persist의 WeatherDAO 클래스
package org.sonatype.mavenbook.weather.persist;

import java.util.ArrayList;
import java.util.List;

import org.hibernate.Query;
import org.hibernate.Session;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import org.sonatype.mavenbook.weather.model.Location;
import org.sonatype.mavenbook.weather.model.Weather;

public class WeatherDAO extends HibernateDaoSupport<-- (1) {

    public WeatherDAO() {}

    public void save(Weather weather) {<-- (2)
      getHibernateTemplate().save( weather );
    }

    public Weather load(Integer id) {<-- (3)
      return (Weather) getHibernateTemplate().load( Weather.class, id);
    }

    @SuppressWarnings("unchecked")
    public List<Weather> recentForLocation( final Location location ) {
      return (List<Weather>) getHibernateTemplate().execute(
        new HibernateCallback() {<-- (4)
        public Object doInHibernate(Session session) {
          Query query = getSession().getNamedQuery("Weather.byLocation");
          query.setParameter("location", location);
          return new ArrayList<Weather>( query.list() );
        }
      });
    }
}

이상과 같다. 아니다. 실제적으로 새로운 행을 입력하고, PK로 조회하고, Location 테이블의 id와 조인하는 Weather의 모든 행을 검색하는 클래스를 작성해야 한다. 명확하게 Hibernate의 복잡함에 대해 이해시키기 위해서는 이 책에 500페이지를 넣어야 하지만, 다음과 같이 매우 간략한 설명을 할 것이다.

(1) 이 클래스는 HibernateDaoSupport 상속받고 있다. 이것이 의미하는 것은 해당 클래스가 Hibernate의 SessionFactory와 연관될 것이며, 이는 Hibernate의 Session 객체를 생성하는데 사용된다. Hibernate에서 모든 오퍼레이션은 Session 객체를 통해 수행한다. Session은 내부의 DB와의 연결을 조정하며 JDBC DataSource와의 연결 관리를 담당한다. HibernateDaoSupport를 상속받는 것 역시 getHibernateTemplate()을 사용해서 HibernateTemplate을 접근할 수 있음을 의미한다.

(2) save() 메소드는 Weather의 인스턴스를 가지고 HibernateTemplate의 save() 메소드를 호출한다. HibernateTemplate은 공통된 Hibernate 오퍼레이션에 대한 호출을 단순화시키며 DB에 특화된 예외를 실행시 예외로 변경한다. 위의 예제에서 save()를 호출하면, Weather테이블에 새로운 레코드를 입력한다. save() 대신 update()를 사용하면, 기존 행을 변경하며, saveOrUpdate()는 Weather의 id 속성이 null인지를 따져서 저장 혹은 변경을 수행한다.

(3) load() 메소드는 다시 한번 HibernateTemplate의 인스턴스의 해당 메소드를 단지 호출하는 한줄짜리 코드이다. HibernateTemplate의 load()는 Class 객체와 Serializable 객체를 파라미터로 받는다. 위의 경우, Serializable는 로딩할 Weather 객체의 id 값에 해당한다.

(4) 마지막 메소드인 recentForLocation() 는 Weather 모델 객체에 정의된 NamedQuery 를 호출한다. 이전의 예로 되돌아가서 살펴보면, Weather 모델 객체는 "from Weather w where w.location = :location" 의 쿼리인 Weather.byLocation 명명 쿼리를 정의했다. HibernateTemplate 의 execute() 메소드에 의해 실행되는 HibernateCallback 내의 Hibernate Session 객체로의 참조를 사용해서 이 NamedQuery 를 로딩하고 있다. 이 메소드에서 recentForLocation() 메소드에 넘겨진 파라미터와 같이 명명된 파라미터 위치를 꺼내는 것을 볼 수 있을 것이다.

이제 몇가지가 명확해졌을 것이다. HibernateDaoSupport와 HibernateTemplate은 Spring 프레임워크의 클래스들이다. Hibernate의 DAO 객체를 쉽게 작성하기 위해 Spring 프레임워크에 의해 생성되었다. 이 DAO를 지원하기 위해서 simple-persist 의 Spring ApplicationContext 정의에 몇가지 설정을 필요로 한다. 예제 7-10에 보여지는 XML 문서는 applicationContext-persist.xml라는 이름의 파일에 src/main/resources에 저장된다.

예제 7-10. simple-persist에 대한 Spring ApplicationContext
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
             http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"
    default-lazy-init="true">

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="annotatedClasses">
            <list>
                <value>org.sonatype.mavenbook.weather.model.Atmosphere</value>
                <value>org.sonatype.mavenbook.weather.model.Condition</value>
                <value>org.sonatype.mavenbook.weather.model.Location</value>
                <value>org.sonatype.mavenbook.weather.model.Weather</value>
                <value>org.sonatype.mavenbook.weather.model.Wind</value>
            </list>
        </property>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.show_sql">false</prop>
                <prop key="hibernate.format_sql">true</prop>
                <prop key="hibernate.transaction.factory_class">
                  org.hibernate.transaction.JDBCTransactionFactory
                </prop>
                <prop key="hibernate.dialect">
                  org.hibernate.dialect.HSQLDialect
                </prop>
                <prop key="hibernate.connection.pool_size">0</prop>
                <prop key="hibernate.connection.driver_class">
                  org.hsqldb.jdbcDriver
                </prop>
                <prop key="hibernate.connection.url">
                  jdbc:hsqldb:data/weather;shutdown=true
                </prop>
                <prop key="hibernate.connection.username">sa</prop>
                <prop key="hibernate.connection.password"></prop>
                <prop key="hibernate.connection.autocommit">true</prop>
                <prop key="hibernate.jdbc.batch_size">0</prop>
            </props>
        </property>
    </bean>

    <bean id="locationDAO"
             class="org.sonatype.mavenbook.weather.persist.LocationDAO">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
   
    <bean id="weatherDAO"
             class="org.sonatype.mavenbook.weather.persist.WeatherDAO">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
 </beans>

이 어플리케이션 컨텍스트에서 몇가지를 수행했다. sessionFactory 빈은 DAO가 Hibernate의 Session 객체를 가지고 오는 빈이다. 이 빈은 AnnotationSessionFactoryBean의 인스턴스이며 annotatedClasses의 목록 형태로 제공된다. 어노테이션이 있는 클래스의 목록은 simple-model 모듈에 정의된 클래스 목록임을 유의하라. 그 다음 sessionFactory는 몇가지 Hibernate 설정 속성 (hibernateProperties)들로 설정된다. 위의 예제에서 Hibernate 속성들은 다음과 같은 것들을 정의한다.

hibernate.dialect
이 세팅은 어떻게 SQL이 해당 DB에 대해 생성되는지를 관리한다. HSQLDB 를 사용하고 있기 때문에 DB dialect는 org.hibernate.dialect.HSQLDialect로 세팅된다. Hibernate는 Oracle, MySQL, Postgres, SQL Server와 같은 모든 주요 DB에 대해 dialect를 가지고 있다.

hibernate.connection.*
위의 예제에서 Spring 설정으로부터 JDBC 연결 속성을 설정하고 있다. 어플리케이션은 ./data/weather 디렉토리의 HSQLDB에 대해 실행하도록 설정된다. 실제 엔터프라이즈 환경에서 어플리케이션의 코드로부터 DB 설정을 없애기 위한 JNDI와 같은 것을 사용할 수도 있을 것이다.

마지막으로 이 빈 정의 파일에서 simple-persist의 DAO 객체 모두가 생성되며 방금 정의한 sessionFactory 빈에 대한 참조가 주어진다. simple-weather의 Spring 어플리케이션 컨텍스트와 같이 이 applicationContext-persist.xml 파일은 더 큰 전사 설계에서 하위 모듈의 아키텍처를 정의한다. 만일 더 큰 저장 클래스들을 가지고 작업하고 있다면, 어플리케이션과는 분리되는 어플리케이션 컨텍스트에 이러한 것들을 포함하는 것이 유용하다는 것을 발견할 것이다.
simple-persist 에서 마지막 퍼즐조각이 있다. 이장의 이후에 어노테이션이 있는 모델 객체로부터 DB 스키마를 생성하는 Maven Hibernate3 플러그인을 어떻게 사용하는지를 살펴볼 것이다. 이를 위해 동작을 올바로 하려면, Maven Hibernate3 플러그인은 JDBC 연결 설정 파라미터, 어노테이션이 있는 클래스 목록, src/main/resources에 hibernate.cfg.xml 라는 파일로부터 다른 Hibernate 설정 등을 읽을 필요가 있다. 이 파일(applicationContext-persist.xml 의 설정 일부분이 겹침)의 목적은 작성된 어노테이션으로부터 Maven Hibernate3 플러그인이 DDL을 생성하도록 만든다. 예제 7-11을 참고하라.

예제 7-11. simple-persist의 hibernate.cfg.xml
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
       
<hibernate-configuration>
  <session-factory>
       
    <!-- SQL dialect -->
    <property name="dialect">org.hibernate.dialect.HSQLDialect</property>
   
    <!-- Database connection settings -->
    <property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
    <property name="connection.url">jdbc:hsqldb:data/weather</property>
    <property name="connection.username">sa</property>
    <property name="connection.password"></property>
    <property name="connection.shutdown">true</property>
   
    <!-- JDBC connection pool (use the built-in one) -->
    <property name="connection.pool_size">1</property>
   
    <!-- Enable Hibernate's automatic session context management -->
    <property name="current_session_context_class">thread</property>
   
    <!-- Disable the second-level cache  -->
    <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
   
    <!-- Echo all executed SQL to stdout -->
    <property name="show_sql">true</property>
   
    <!-- disable batching so HSQLDB will propagate errors correctly. -->
    <property name="jdbc.batch_size">0</property>
   
    <!-- List all the mapping documents we're using -->
    <mapping class="org.sonatype.mavenbook.weather.model.Atmosphere"/>
    <mapping class="org.sonatype.mavenbook.weather.model.Condition"/>
    <mapping class="org.sonatype.mavenbook.weather.model.Location"/>
    <mapping class="org.sonatype.mavenbook.weather.model.Weather"/>
    <mapping class="org.sonatype.mavenbook.weather.model.Wind"/>
       
  </session-factory>
</hibernate-configuration>

예제 7-10과 예제 7-11의 내용은 겹친다. Spring 어플리케이션 컨텍스트 XML은 웹 어플리케이션과 명령행 어플리케이션에 의해 사용되는 반면에, hibernate.cfg.xml는 메이븐의 Hibernate3 플러그인을 지원하는 용도로만 사용된다. 이장의 이후에 이 hibernate.cfg.xml 와 simple-model에 정의된 어노테이션이 있는 객체 모델을 기반으로 DB 스키마를 생성하는 Maven Hibernate3 플러그인을 어떻게 사용하는지를 살펴볼 것이다. hibernate.cfg.xml 파일은 JDBC 연결 속성을 설정하고 Maven Hibernate3 플러그인에 대해 어노테이션이 있는 모델 클래스의 목록을 나열하는 파일이다.

7.6 간단한 웹 어플리케이션 모듈

웹 어플리케이션은 simple-webapp 프로젝트에 정의된다. simple 웹 어플리케이션 프로젝트는 WeatherController과 HistoryController이라는 두개의 Spring MVC 컨트롤러를 정의하게 된다. 이 두개의 컨트롤러는 simple-weather와 simple-persist에 정의된 컴포넌트를 참조하게 된다. Spring 컨테이너는 simple-weather의 applicationContext-weather.xml 파일과 simple-persist의 applicationContext-persist.xml 파일을 참조하는 어플리케이션의 web.xml 에 설정된다. 이 간단한 웹 어플리케이션의 컴포넌트 아키텍처가 그림 7-3에 나타나 있다.

그림 7-3. simple-weather과 simple-persist의 컴포넌트를 참조하는 Spring MVC 컨트롤러

simple-webapp을 위한 POM이 예제 7-12에 나타난다.

예제 7-12. 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.ch07</groupId>
    <artifactId>simple-parent</artifactId>
    <version>1.0</version>
  </parent>

  <artifactId>simple-webapp</artifactId>
  <packaging>war</packaging>
  <name>Simple Web Application</name>
  <dependencies>
    <dependency> <-- (1)
      <groupId>org.apache.geronimo.specs</groupId>
      <artifactId>geronimo-servlet_2.4_spec</artifactId>
      <version>1.1.1</version>
    </dependency>
    <dependency>
      <groupId>org.sonatype.mavenbook.ch07</groupId>
      <artifactId>simple-weather</artifactId>
      <version>1.0</version>
    </dependency>
    <dependency>
      <groupId>org.sonatype.mavenbook.ch07</groupId>
      <artifactId>simple-persist</artifactId>
      <version>1.0</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring</artifactId>
      <version>2.0.7</version>
    </dependency>
    <dependency>
      <groupId>org.apache.velocity</groupId>
      <artifactId>velocity</artifactId>
      <version>1.5</version>
    </dependency>
  </dependencies>
  <build>
    <finalName>simple-webapp</finalName>
    <plugins>
      <plugin> <-- (2)
        <groupId>org.mortbay.jetty</groupId>
        <artifactId>maven-jetty-plugin</artifactId>
        <dependencies> <-- (3)
          <dependency>
            <groupId>hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <version>1.8.0.7</version>
          </dependency> <-- (4)
        </dependencies>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId> <-- (5)
        <artifactId>hibernate3-maven-plugin</artifactId>
        <version>2.0</version>
        <configuration>
          <components>
            <component>
              <name>hbm2ddl</name>
              <implementation>annotationconfiguration</implementation> <-- (6)
            </component>
          </components>
        </configuration>
        <dependencies>
          <dependency>
            <groupId>hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <version>1.8.0.7</version>
          </dependency>
        </dependencies>
      </plugin>
    </plugins>
  </build>
</project>

이책이 진행함에 따라 그리고 예제가 점점 더 커짐에 따라 pom.xml이 어떤 무게를 가지기 시작함을 느낄 수 있을 것이다. 위의 POM에서 네개의 의존관계와 두개의 플러그인을 설정하고 있다. 이 POM을 세부적으로 살펴보고 몇가지 중요한 설정 부분에 대해 생각해보도록 하자.

(1) simple-webapp 프로젝트는 서블릿 2.4 명세, Apache Geronimo의 구현체, simple-weather 서비스 라이브러리, simple-persist 저장 라이브러리, 전체 Spring 프레임워크 2.0.7의 네개의 의존관계를 정의하고 있다.

(2) 빌드 설정에서 내장 HSQLDB 인스턴스에 접속하는 Maven Hibernate3 플러그인을 설정하고 있다. JDBC를 사용해서 이 DB에 성공적으로 접속하는 Maven Hibernate3 플러그인에 대해 플러그인은 클래스패스의 HSQLDB JDBC 드라이버를 참조할 필요가 있다. 의존관계를 플러그인에 대해 사용가능하게 하려면 확장으로 의존관계에 대한 참조를 추가한다. 확장을 플러그인 실행에 대한 클래스패스에 어떤 것을 추가하는 능력을 제공하는 것으로 생각할 수 있다. 이 경우 hsqldb:hsqldb:1.8.0.7를 참조하고 있다.

(3) Maven Jetty 플러그인은 위의 프로젝트에 추가하는 것만큼 더 쉽게 할 수는 없을 것이다. 적절한 groupId와 artifactId를 참조하는 plugin 요소를 단순히 추가한다. 이 플러그인이 설정하는데 너무 하찮은 것이라는 의미는 플러그인 개발자가 대부분의 경우에 변경할 필요가 없는 적합한 기본값을 제공하는 작업을 해주었다는 것을 뜻한다. 만일 Jetty 플러그인의 설정을 변경할 필요가 정말로 있다면 configuration 요소를 추가함으로써 그와 같이 할 수 있을 것이다.

(4) 빌드 설정에서 내장 HSQLDB 인스턴스를 접속하는 Maven Hibernate3 플러그인을 설정하고 있다. JDBC를 사용해서 이 DB에 성공적으로 접속하는 Maven Hibernate3 플러그인에 대해 플러그인은 클래스패스의 HSQLDB JDBC 드라이버를 참조할 필요가 있다. 의존관계를 플러그인에 대해 사용가능하게 하려면 <plugin> 선언 내에 바로 <dependency> 선언을 추가한다. 이 경우, hsqldb:hsqldb:1.8.0.7를 참조하고 있다. Hibernate 플러그인은 또한 DB를 생성하기 위한 JDBC 드라이버를 필요로 하고 있어서 설정에 이 의존관계 역시 추가했다.

(5) Maven Hibernate 플러그인 해당 POM이 시작할 때 작동하게 된다. 다음 절에서 HSQLDB DB를 생성하기 위해 hbm2ddl goal 을 실행할 것이다. 위의 pom.xml에서 Codehaus Mojo 플러그인에서 만든 hibernate3-maven-plugin 의 2.0 버전에 대한 참고를 포함하고 있다.

(6) Maven Hibernate3 플러그인은 Hibernate3 플러그인의 서로 다른 사용 시나리오에 적절한 Hibernate 매핑 정보를 획득하는 서로 다른 방법을 가진다. 만일 Hibernate 매핑 XML (.hbm.xml) 파일을 사용하고, hbm2java goal을 사용해서 모델 클래스들을 생성하고자 원한다면 configuration에 implementation을 세팅할 것이다. 만일 기존 DB에서 .hbm.xml 파일과 모델 클래스들을 만드는데 DB 역공학을 위해 Hibernate3 플러그인을 사용하려면, jdbcconfiguration의 implementation을 사용할 것이다. 이 경우, DB를 생성하기 위해 기존 어노테이션이 있는 객체 모델을 사용하면 된다. 즉, Hibernate 매핑을 가지고 있지만, 아직 DB는 가지고 있지 않다. 이러한 사용 시나리오에서 적절한 implementation 값은 annotationconfiguration 이다. Maven Hibernate3 플러그인은 이후 7.7 절에서 더 상세하게 설명한다.

유의
일반적인 실수가 플러그인에 의해 필요한 의존관계를 추가하는데 <extensions> 설정을 사용한다는 것이다. 이는 매우 피해야 하는 상황인데, 확장은 프로젝트 간, 다른 성가신 부수효과 간의 클래스패스 오염을 유발시킬 수 있기 때문이다. 추가로 <extensions> 행위는 2.1에서 재작업되고 있는데, 따라서 어떠한 경우든 결국 변경할 필요가 있을 것이다. 오로지 <extensions> 대한 일반적인 사용은 새로운 wagon 구현을 정의하는 것이다.

다음은 모든 요청을 처리하는 두개의 Spring MVC 컨트롤러로 관심을 옮긴다 이 두개의 컨트롤러는 simple-weather와 simple-persist에 정의된 빈을 참조하고 있다. 예제 7-13을 참고하라.

예제 7-13. simple-webapp 의 WeatherController
package org.sonatype.mavenbook.web;

import org.sonatype.mavenbook.weather.model.Weather;
import org.sonatype.mavenbook.weather.persist.WeatherDAO;
import org.sonatype.mavenbook.weather.WeatherService;
import javax.servlet.http.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

public class WeatherController implements Controller {

  private WeatherService weatherService;
  private WeatherDAO weatherDAO;

  public ModelAndView handleRequest(HttpServletRequest request,
      HttpServletResponse response) throws Exception {

    String zip = request.getParameter("zip");
    Weather weather = weatherService.retrieveForecast(zip);
    weatherDAO.save(weather);
    return new ModelAndView("weather", "weather", weather);
  }

  public WeatherService getWeatherService() {
    return weatherService;
  }

  public void setWeatherService(WeatherService weatherService) {
    this.weatherService = weatherService;
  }

  public WeatherDAO getWeatherDAO() {
    return weatherDAO;
  }

  public void setWeatherDAO(WeatherDAO weatherDAO) {
    this.weatherDAO = weatherDAO;
  }
}

WeatherController는 예제에 나타난 시그너처를 사용해 handleRequest() 메소드를 구현하도록 하는 Spring MVC의 Controller 인터페이스를 구현하고 있다. 만일 이 메소드의 본질을 보려면, weatherService 인스턴스 변수의 retrieveForecast() 메소드를 호출하는 것을 볼 수 있을 것이다. WeatherService 클래스를 인스턴스화 시키는 서블릿을 가지는 이전 장과 달리, WeatherController는 weatherService 속성을 가진 빈이다. Spring IoC 컨테이너는 weatherService 컴포넌트에 컨트롤러를 엮는(wiring) 것을 담당하고 있다. 또한 이 Spring 컨트롤러 구현에서 WeatherFormatter를 사용하고 있지 않음을 유의하라. 대신에 ModelAndView의 생성자에게 retrieveForecast()에 의해 반환되는 Weather객체를 넘기고 있다. ModelAndView클래스는 Velocity 템플릿을 사용하게 되며, 이 템플릿은 ${weather} 변수에 대한 참조를 가진다. weather.vm 템플릿은 src/main/webapp/WEB-INF/vm에 저장되며 예제 7-14에 나타난다.
WeatherController에서 예상날씨를 출력하기 전에 WeatherService로부터 반환받은 Weather객체를 WeatherDAO의 save() 메소드로 넘긴다. 여기서 Hibernate를 사용해서 HSQLDB에 Weather객체를 저장한다. 나중에 HistoryController에서 WeatherController가 저장했던 날씨 예상에 관한 이력을 어떻게 조회할 수 있는지를 살펴볼 것이다.

예제 7-14. WeatherController 에서 호출하는 weather.vm 템플릿
<b>Current Weather Conditions for:
  ${weather.location.city}, ${weather.location.region},
  ${weather.location.country}</b><br/>
 
<ul>
  <li>Temperature: ${weather.condition.temp}</li>
  <li>Condition: ${weather.condition.text}</li>
  <li>Humidity: ${weather.atmosphere.humidity}</li>
  <li>Wind Chill: ${weather.wind.chill}</li>
  <li>Date: ${weather.date}</li>
</ul>

Velocity 템플릿의 문법은 직관적이다. 변수는 ${} 표기를 사용해서 참조된다. 중괄화 사이의 표현식은 WeatherController에 의해 해당 템플릿으로 넘겨진 weather 변수의 속성, 혹은 속성의 속성을 참조한다.
HistoryController는 WeatherController에 의해 요청되었던 최근 날씨정보를 조회하는데 사용된다. WeatherController로부터 날씨정보를 조회할때마다 해당 컨트롤러는 WeatherDAO를 통해 DB에 Weather 객체를 저장한다. WeatherDAO는 그 다음에 관련 DB 테이블들의 여러 행들에 Weather 객체를 분해하기 위해 Hibernate를 사용한다. HistoryController는 예제 7-15에 보여진다.

예제 7-15. simple-web의 HistoryController
package org.sonatype.mavenbook.web;

import java.util.*;
import javax.servlet.http.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import org.sonatype.mavenbook.weather.model.*;
import org.sonatype.mavenbook.weather.persist.*;

public class HistoryController implements Controller {

  private LocationDAO locationDAO;
  private WeatherDAO weatherDAO;

  public ModelAndView handleRequest(HttpServletRequest request,
      HttpServletResponse response) throws Exception {
    String zip = request.getParameter("zip");
    Location location = locationDAO.findByZip(zip);
    List<Weather> weathers = weatherDAO.recentForLocation( location );

    Map<String,Object> model = new HashMap<String,Object>();
    model.put( "location", location );
    model.put( "weathers", weathers );

    return new ModelAndView("history", model);
  }

  public WeatherDAO getWeatherDAO() {
    return weatherDAO;
  }

  public void setWeatherDAO(WeatherDAO weatherDAO) {
    this.weatherDAO = weatherDAO;
  }

  public LocationDAO getLocationDAO() {
    return locationDAO;
  }

  public void setLocationDAO(LocationDAO locationDAO) {
    this.locationDAO = locationDAO;
  }
}

HistoryController는 simple-persist에 정의된 두개의 DAO 객체에 엮여져있다. DAO는 HistoryController의 빈 속석으로 WeatherDAO와 LocationDAO이다. HistoryController의 목적은 우편번호 파라미터에 대응하는 Weather 객체의 List를 조회하는 것이다. WeatherDAO가 DB에 Weather 객체를 저장할 때 우편번호만을 저장하지 않는다. simple-model의 Weather 객체와 관련된 Location 객체를 저장한다. Weather 객체의 List를 조회하려면 HistoryController는 먼저 우편번호 파라미터에 해당하는 Location 객체르 조회한다. 이는 LocationDAO의 findByZip() 메소드를 호출함으로써 수행한다.
일단 Location 객체가 조회되었다면, 그 다음에 HistoryController 는 주어진 Location과 일치하는 최근의 Weather 객체들을 조회하려고 할 것이다. 일단 List<Weather>가 조회되었다면, HashMap이 예제 7-16에 보여지는 history.vm Velocity 템플릿에 대한 두개의 변수를 담기 위해 생성된다.

예제 7-16. HistoryController에 의해 사용되는 history.vm
<b>
Weather History for: ${location.city}, ${location.region}, ${location.country}
</b>
<br/>
 
#foreach( $weather in $weathers )
  <ul>
    <li>Temperature: $weather.condition.temp</li>
    <li>Condition: $weather.condition.text</li>
    <li>Humidity: $weather.atmosphere.humidity</li>
    <li>Wind Chill: $weather.wind.chill</li>
    <li>Date: $weather.date</li>
  </ul>
#end

src/main/webapp/WEB-INF/vm 에 있는 history.vm 템플릿은 WeatherDAO에 의해서 조회되는 날씨 정보의 위치에 대한 정보를 출력하기 위해 location 변수를 참조한다. 이 템플릿은 그 다음에 weathers 변수의 각 요소를 접근하는 접근하기 위해 Velocity 제어 구조인 #foreach를 사용한다. weathers의 각 요소는 weather라는 이름의 변수에 할당되며, #foreach와 #end 사이의 템플릿은 각 날씨정보에 대해 조회한다.
이러한 Controller 구현에 대해서 이미 보았고, simple-weather와 simple-persist에 정의된 다른 빈들을 참조하는 것을 보았다. 이들은 HTTP 요청에 대해 응답하고, Velocity 템플릿을 사용하는 방법을 아는 템플릿 시스템으로 제어를 이양한다. 모든 이러한 내용은 src/main/webapp/WEB-INF/weather-servlet.xml 에 있는 Spring의 어플리케이션 컨텍스트에서 설정된다. ServletContextListener에 의해 로딩되며, 클래스패스로부터 applicationContext-weather.xml과 applicationContext-persist.xml를 로딩하기 위해 또한 설정된다. 예제 7-17에 있는 weather-servlet.xml을 더 자세하게 살펴보자.

예제 7-17. Spring 컨트롤러 설정 weather-servlet.xml
<beans> 
     <bean id="weatherController" <-- (1)
           class="org.sonatype.mavenbook.web.WeatherController">
       <property name="weatherService" ref="weatherService"/>
       <property name="weatherDAO" ref="weatherDAO"/>
     </bean>

     <bean id="historyController"
           class="org.sonatype.mavenbook.web.HistoryController">
       <property name="weatherDAO" ref="weatherDAO"/>
       <property name="locationDAO" ref="locationDAO"/>
     </bean>

     <!-- you can have more than one handler defined -->
     <bean id="urlMapping"
          class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
          <property name="urlMap">
               <map>
                    <entry key="/weather.x"> <-- (2)
                         <ref bean="weatherController" />
                    </entry>
                    <entry key="/history.x">
                         <ref bean="historyController" />
                    </entry>
               </map>
          </property>
     </bean>


     <bean id="velocityConfig" <-- (3)
           class="org.springframework.web.servlet.view.velocity.VelocityConfigurer">
       <property name="resourceLoaderPath" value="/WEB-INF/vm/"/>
     </bean>

     <bean id="viewResolver" <-- (4)
           class="org.springframework.web.servlet.view.velocity.VelocityViewResolver">
       <property name="cache" value="true"/>
       <property name="prefix" value=""/>
       <property name="suffix" value=".vm"/>
       <property name="exposeSpringMacroHelpers" value="true"/>
     </bean>
</beans>

(1) weather-servlet.xml 은 Spring 관리 빈으로 두개의 컨트롤러를 정의한다. weatherController는 weatherService와 weatherDAO로 참조하는 두개의 속성을 가진다. historyController는 weatherDAO와 locationDAO 빈을 참조한다. ApplicationContext가 생성될 때 simple-persist와 simple-weather 모두에서 정의된 ApplicationContexts로 접근을 하는 환경에서 생성된다. 예제 7-18에서 Spring이 어떻게 여러 Spring 설정 파일로부터 컴포넌트들을 병합하기 위해 설정되는지를 살펴볼 수 있다.

(2) urlMapping 빈은 WeatherController와 HistoryController를 호출하는 URL 패턴을 정의한다. 위의 예제에서 SimpleUrlHandlerMapping을 사용하고 /weather.x을 WeatherController로 그리고 /history.x를 HistoryController로 매핑하고 있다.

(3) Velocity 템플릿 엔진을 사용하고 있기 때문에 몇가지 설정 옵션을 넘길 필요가 있다. velocityConfig 빈에서 Velocity에게 /WEB-INF/vm 디렉토리에서 모든 템플릿들을 찾도록 전달하고 있다.

(4) 마지막으로 viewResolver는 VelocityViewResolver 클래스와 같이 설정된다. JSP나 JSTL(JavaServer Pages Standard Tag Library) 페이지를 처리하는 표준 ViewResolver에서 FreeMarker 템플릿을 처리할 수 있는 resolver로 Spring에는 수많은 ViewResolver 구현체들이 있다. 위의 예제에서 Velocity 템플릿 엔진을 설정하고 ModelAndView로 넘기는 해당 템플릿의 이름으로 자동으로 추가되는 기본 접두어 및 접미어를 세팅하고 있다.

따라서, simple-webapp 프로젝트는 웹 어플리케이션에 대한 기본적인 설정을 제공하는 web.xml 이었다. web.xml는 예제 7-18에 나타난다.

예제 7-18. simple-webapp 에 대한 web.xml
<web-app id="simple-webapp" version="2.4"
     xmlns="http://java.sun.com/xml/ns/j2ee"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
                         http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
  <display-name>Simple Web Application</display-name>
 
  <context-param> <-- (1)
    <param-name>contextConfigLocation</param-name>
    <param-value>
      classpath:applicationContext-weather.xml
      classpath:applicationContext-persist.xml
    </param-value>
  </context-param>
 
  <context-param> <-- (2)
    <param-name>log4jConfigLocation</param-name>
    <param-value>/WEB-INF/log4j.properties</param-value>
  </context-param>
 
  <listener> <-- (3)
    <listener-class>
      org.springframework.web.util.Log4jConfigListener
    </listener-class>
  </listener>
 
  <listener>
    <listener-class> <-- (4)
     org.springframework.web.context.ContextLoaderListener
    </listener-class>
  </listener>
 
  <servlet> <-- (5)
    <servlet-name>weather</servlet-name>
    <servlet-class>
      org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping> <-- (6)
    <servlet-name>weather</servlet-name>
    <url-pattern>*.x</url-pattern>
  </servlet-mapping>
</web-app>

(1) 여기서 이 프로젝트에서 applicationContext-weather.xml과 applicationContext-persist.xml을 재사용할 수 있는 약간의 마술이 있다. contextConfigLocation은 ApplicationContext을 생성하기 위해 ContextLoaderListener에 의해 사용된다. weather 서블릿이 생성될 때, 예제 7-17의 weather-servlet.xml은 이 contextConfigLocation로부터 생성된 ApplicationContext와 같이 사용될 것이다. 이러한 방법으로 또 다른 프로젝트에서 빈들을 정으할 수 있고 클래스패스를 통해 이러한 빈들을 참조할 수 있다. simple-persist와 simple-weather JAR들이 WEB-INF/lib에 위치할 것이기 때문에 해당 파일들을 참조하는 classpath:접두어를 사용하기만 하면 된다. (또 다른 방법은 해당 파일들을 /WEB-INF에 복사하고 /WEB-INF/applicationContext-persist.xml와 같은 방식으로 참조하는 것이다.)

(2) log4jConfigLocation은 Log4J 로깅 설정을 검색하는 Log4JConfigListener에 전달하는데 사용된다. 위의 예제에서 /WEB-INF/log4j.properties에서 검색하도록 Log4J에게 전달하고 있다.

(3) 웹 어플리케이션이 시작할 때 Log4J 시스템이 설정됨을 보장한다. ContextLoaderListener 이전에 Log4JConfigListener를 넣는 것이 중요하다. 그렇지 않으면 어플리케이션 기동을 막는 문제를 집어내는 중요한 로깅 메시지를 놓칠 수도 있다. 특히 많은 양의 빈들이 Spring에 의해서 관리되고 이 빈들 중 하나가 어플리케이션 기동에서 놓치게 되는 일이 발생하면 어플리케이션은 실패할 것이다. Spring이 시작하기 전에 초기의 로깅을 남긴다면 경고나 에러를 포착할 수 있는 기회를 가질 것이다. Spring이 시작하기전에 초기에 로깅을 남기지 못한다면 왜 어플리케이션이 기동하지 못했는지에 대한 아무런 내용을 알지 못할 것이다.

(4) ContextLoaderListener는 기본적으로 Spring 컨테이너이다. 어플리케이션이 시작할 때 이 리스너는 contextConfigLocation 파라미터로부터 ApplicationContext를 생성하게 된다.

(5) weather라는 이름을 가진 Spring MVC인 DispatcherServlet를 정의한다. 이것은 Spring에게 /WEB-INF/weather-servlet.xml에서 Spring 설정 파일을 찾도록 한다. 필요한 만큼 많은 DispatcherServlet를 선언할 수 있다. DispatcherServlet은 하나 이상의 Spring MVC 컨트롤로 구현체를 포함할 수 있다.

(6) .x로 끝나는 모든 요청은 weather 서블릿으로 보내진다. .x 확장자는 아무런 의미가 없음을 유의하라. 임의의 값을 사용했으며 선호하는 URL 패턴을 사용할 수 있다.

7.7 웹 어플리케이션 실행

웹 어플리케이션을 실행하려면 먼저 Hibernate3 플러그인을 사용해서 DB를 만들어야 한다. 이렇게 하기 위해 simple-webapp 프로젝트 디렉토리에서 다음을 실행한다.

$ mvn hibernate3:hbm2ddl
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'hibernate3'.
[INFO] org.codehaus.mojo: checking for updates from central
[INFO] -------------------------------------------------------------
[INFO] Building Chapter 7 Simple Web Application
[INFO]    task-segment: [hibernate3:hbm2ddl]
[INFO] -------------------------------------------------------------
[INFO] Preparing hibernate3:hbm2ddl
...
10:24:56,151  INFO org.hibernate.tool.hbm2ddl.SchemaExport - schema\
          export complete
[INFO] -------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] -------------------------------------------------------------

이와 같이 실행하고 나면 HSQLDB DB를 포함하는 ${basedir}/data 디렉토리가 있을 것이다. 그 다음에 웹 어플리케이션을 다음과 같이 실행할 수 있다.

$ mvn jetty:run
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'jetty'.
[INFO] ---------------------------------------------------------------
[INFO] Building Chapter 7 Simple Web Application
[INFO]    task-segment: [jetty:run]
[INFO] ---------------------------------------------------------------
[INFO] Preparing jetty:run
...
[INFO] [jetty:run]
[INFO] Configuring Jetty for project: Chapter 7 Simple Web Application
...
[INFO] Context path = /simple-webapp
[INFO] Tmp directory =  determined at runtime
[INFO] Web defaults = org/mortbay/jetty/webapp/webdefault.xml
[INFO] Web overrides =  none
[INFO] Starting jetty 6.1.7 ...
2008-03-25 10:28:03.639::INFO:  jetty-6.1.7
...
2147 INFO  DispatcherServlet  - FrameworkServlet 'weather':
         initialization completed in 1654 ms
2008-03-25 10:28:06.341::INFO: 
         Started SelectChannelConnector@0.0.0.0:8080
[INFO] Started Jetty Server

일단 Jetty가 시작되면, http://localhost:8080/simple-webapp/weather.x?zip=60202 를 접속할 수 있으며, 웹 브라우저에서 일리노이즈 주의 에반스톤의 날씨를 볼 수 있다. 우편번호를 바꾸면 해당 지역의 날씨정보를 볼 수 있을 것이다.

Current Weather Conditions for: Evanston, IL, US

    * Temperature: 42
    * Condition: Partly Cloudy
    * Humidity: 55
    * Wind Chill: 34
    * Date: Tue Mar 25 10:29:45 CDT 2008


7.8 simple-command 모듈

simple-command 프로젝트는 simple-webapp의 명령행 버전이다. 동일한 의존관계인 simple-persist와 simple-weather에 의존하고 있는 유틸리티이다. 웹 브라우저를 통해 해당 어플리케이션과 상호작용하는 대신에 명령행에서 simple-command 유틸리티를 실행하게 된다. 그림 7-4와 예제 7-19를 참고하라.

그림 7-4. simple-command 모듈

예제 7-19. simple-command에 대한 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.ch07</groupId>
    <artifactId>simple-parent</artifactId>
    <version>1.0</version>
  </parent>

  <artifactId>simple-command</artifactId>
  <packaging>jar</packaging>
  <name>Simple Command Line Tool</name>

  <build>
    <finalName>${project.artifactId}</finalName>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <configuration>
          <source>1.5</source>
          <target>1.5</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <configuration>
          <testFailureIgnore>true</testFailureIgnore>
        </configuration>
      </plugin>
      <plugin>
       <artifactId>maven-assembly-plugin</artifactId>
        <configuration>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>hibernate3-maven-plugin</artifactId>
        <version>2.1</version>
        <configuration>
          <components>
            <component>
              <name>hbm2ddl</name>
              <implementation>annotationconfiguration</implementation>
            </component>
          </components>
        </configuration>
        <dependencies>
          <dependency>
            <groupId>hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <version>1.8.0.7</version>
          </dependency>
        </dependencies>
      </plugin>
    </plugins>
  </build>

  <dependencies>
    <dependency>
      <groupId>org.sonatype.mavenbook.ch07</groupId>
      <artifactId>simple-weather</artifactId>
      <version>1.0</version>
    </dependency>
    <dependency>
      <groupId>org.sonatype.mavenbook.ch07</groupId>
      <artifactId>simple-persist</artifactId>
      <version>1.0</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring</artifactId>
      <version>2.0.7</version>
    </dependency>
    <dependency>
      <groupId>hsqldb</groupId>
      <artifactId>hsqldb</artifactId>
      <version>1.8.0.7</version>
    </dependency>
  </dependencies>
</project>

위의 POM은 예제 7-20에 나타난 org.sonatype.mavenbook.weather.Main 클래스를 포함하는 JAR 파일을 생성한다. 해당 POM에서 빌드하는 프로젝트로부터의 바이트코드와 모든 의존관계 바이트코드를 포함하여 프로젝트가 실행하는데 필요한 모든 바이트코드를 포함하는 단일 JAR 파일을 생성하는 jar-with-dependencies라는 이름의 내장 어셈블리 디스크립터를 사용하는 Maven Assembly 플러그인을 설정한다.

예제 7-20. simple-command에 대한 Main 클래스
package org.sonatype.mavenbook.weather;

import java.util.List;

import org.apache.log4j.PropertyConfigurator;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import org.sonatype.mavenbook.weather.model.Location;
import org.sonatype.mavenbook.weather.model.Weather;
import org.sonatype.mavenbook.weather.persist.LocationDAO;
import org.sonatype.mavenbook.weather.persist.WeatherDAO;

public class Main {

  private WeatherService weatherService;
  private WeatherDAO weatherDAO;
  private LocationDAO locationDAO;

  public static void main(String[] args) throws Exception {
    // Configure Log4J
    PropertyConfigurator.configure(Main.class.getClassLoader().getResource(
        "log4j.properties"));

    // Read the Zip Code from the Command-line (if none supplied, use 60202)
    String zipcode = "60202";
    try {
      zipcode = args[0];
    } catch (Exception e) {
    }

    // Read the Operation from the Command-line (if none supplied use weather)
    String operation = "weather";
    try {
      operation = args[1];
    } catch (Exception e) {
    }

    // Start the program
    Main main = new Main(zipcode);

    ApplicationContext context =
      new ClassPathXmlApplicationContext(
        new String[] { "classpath:applicationContext-weather.xml",
                       "classpath:applicationContext-persist.xml" });
    main.weatherService = (WeatherService) context.getBean("weatherService");
    main.locationDAO = (LocationDAO) context.getBean("locationDAO");
    main.weatherDAO = (WeatherDAO) context.getBean("weatherDAO");
    if( operation.equals("weather")) {
      main.getWeather();
    } else {
      main.getHistory();
    }
  }

  private String zip;

  public Main(String zip) {
    this.zip = zip;
  }

  public void getWeather() throws Exception {
    Weather weather = weatherService.retrieveForecast(zip);
    weatherDAO.save( weather );
    System.out.print(new WeatherFormatter().formatWeather(weather));
  }

  public void getHistory() throws Exception {
    Location location = locationDAO.findByZip(zip);
    List<Weather> weathers = weatherDAO.recentForLocation(location);
    System.out.print(new WeatherFormatter().formatHistory(location, weathers));
  }
}

Main 클래스는 WeatherDAO, LocationDAO, WeatherService 를 참조한다. 이 클래스의 정적 main() 메소드는

  • 첫번째 명령행 인자로부터 우편번호를 읽는다.
  • 두번째 명령행 인자로부터 operation을 읽는다. 만일 operation인 “weather”이면 가장 최근의 날씨가 웹 서비스로부터 조회한다. 만일 operation이 “history”이면, 프로그램은 로컬 DB에서 이력으로 관리되는 날씨 정보를 조회한다.
  • simple-persist와 simple-weather로부터 로딩되는 두개의 XML 파일을 사용해서 Spring의 ApplicationContext 를 로딩한다.
  • Main의 인스턴스를 생성한다.
  • Spring의 ApplicationContext로부터 빈들과 같이 weatherService, weatherDAO, locationDAO를 꺼낸다.
  • 지정된 operation에 따라서 적절한 메소드인 getWeather()나 getHistory()를 실행한다.

웹 어플리케이션에서 Velocity 템플릿을 로딩하는 Spring의 VelocityViewResolver를 사용했다. 독립 구현체에서는 Velocity 템플릿을 가지고 날씨 데이터를 로딩하는 간단한 클래스를 작성할 필요가 있다. 예제 7-21은 날씨 보고와 날씨 이력을 조회하는 두개의 메소드를 가진 클래스인 WeatherFormatter의 내용이다.

예제 7-21. Velocity 템플릿을 사용하는 날씨 데이터를 조회하는 WeatherFormatter
package org.sonatype.mavenbook.weather;

import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.util.List;

import org.apache.log4j.Logger;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;

import org.sonatype.mavenbook.weather.model.Location;
import org.sonatype.mavenbook.weather.model.Weather;

public class WeatherFormatter {

  private static Logger log = Logger.getLogger(WeatherFormatter.class);

  public String formatWeather( Weather weather ) throws Exception {
    log.info( "Formatting Weather Data" );
    Reader reader =
      new InputStreamReader( getClass().getClassLoader().
                                 getResourceAsStream("weather.vm"));
    VelocityContext context = new VelocityContext();
    context.put("weather", weather );
    StringWriter writer = new StringWriter();
    Velocity.evaluate(context, writer, "", reader);
    return writer.toString();
  }

  public String formatHistory( Location location, List<Weather> weathers ) 
        throws Exception {
    log.info( "Formatting History Data" );
    Reader reader =
      new InputStreamReader( getClass().getClassLoader().
                                 getResourceAsStream("history.vm"));
    VelocityContext context = new VelocityContext();
    context.put("location", location );
    context.put("weathers", weathers );
    StringWriter writer = new StringWriter();
    Velocity.evaluate(context, writer, "", reader);
    return writer.toString();
  }
}

weather.vm 템플릿은 예제 7-22에 나타난 바와 같이 우편번호의 도시, 국가, 지역 뿐만 아니라 현재 온도를 단순히 보여준다. history.vm 템플릿은 지역을 보여주고 그 다음에 예제 7-23에 나타난 바와 같이 로컬 DB에 저장된 날씨 정보들을 반복한다. 이 템플릿 모두 ${basedir}/src/main/resources에 있다.

예제 7-22. weather.vm Velocity 템플릿
****************************************
Current Weather Conditions for:
  ${weather.location.city},
  ${weather.location.region},
  ${weather.location.country}
****************************************

 * Temperature: ${weather.condition.temp}
 * Condition: ${weather.condition.text}
 * Humidity: ${weather.atmosphere.humidity}
 * Wind Chill: ${weather.wind.chill}
 * Date: ${weather.date}

예제 7-23. history.vm Velocity 템플릿
Weather History for:
${location.city},
${location.region},
${location.country}


#foreach( $weather in $weathers )
****************************************
 * Temperature: $weather.condition.temp
 * Condition: $weather.condition.text
 * Humidity: $weather.atmosphere.humidity
 * Wind Chill: $weather.wind.chill
 * Date: $weather.date
#end


7.9 simple-command 실행

simple-command 프로젝트는 프로젝트의 바이트코드와 의존관계의 모든 바이트코드를 포함하는 단일 JAR를 생성하도록 설정된다. 이 어셈블리를 생성하려면, simple-command 프로젝트 디렉토로부터 Maven Assembly 플러그인의 assembly goal을 실행한다.

$ mvn assembly:assembly
[INFO] ------------------------------------------------------------------------
[INFO] Building Chapter 7 Simple Command Line Tool
[INFO]    task-segment: [assembly:assembly] (aggregator-style)
[INFO] ------------------------------------------------------------------------
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] Nothing to compile - all classes are up to date
[INFO] [resources:testResources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:testCompile]
[INFO] Nothing to compile - all classes are up to date
[INFO] [surefire:test]
...
[INFO] [jar:jar]
[INFO] Building jar: .../simple-parent/simple-command/target/simple-command.jar
[INFO] [assembly:assembly]
[INFO] Processing DependencySet (output=)
[INFO] Expanding: .../.m2/repository/.../simple-weather-1-SNAPSHOT.jar into \
                                    /tmp/archived-file-set.93251505.tmp
[INFO] Expanding: .../.m2/repository/.../simple-model-1-SNAPSHOT.jar into \
                                    /tmp/archived-file-set.2012480870.tmp
[INFO] Expanding: .../.m2/repository/../hibernate-3.2.5.ga.jar into \
                                    /tmp/archived-file-set.1296516202.tmp
... skipping 25 lines of dependency unpacking ...
[INFO] Expanding: .../.m2/repository/.../velocity-1.5.jar into \
                                    /tmp/archived-file-set.379482226.tmp
[INFO] Expanding: .../.m2/repository/.../commons-lang-2.1.jar into \
                                    /tmp/archived-file-set.1329200163.tmp
[INFO] Expanding: .../.m2/repository/.../oro-2.0.8.jar into \
                                    /tmp/archived-file-set.1993155327.tmp
[INFO] Building jar: .../simple-parent/simple-command/target/\
                                      simple-command-jar-with-dependencies.jar

빌드는 바이트코드를 컴파일하고, 테스트를 실행하고, 최종적으로 프로젝트에 대한 JAR를 빌드하는 생명주기를 통해 진행된다. 그 다음에 assembly:assembly goal은 임시 디렉토리에 모든 의존관계를 풀어서 의존관계와 같이 JAR 를 생성한 다음에 simple-command-jar-with-dependencies.jar 라는 이름으로 target/에 단일 JAR로 모든 바이트코드를 묶는다. 이렇게 만들어진 JAR는 15MB 정도가 된다.
command-line 도구를 실행하기 전에 HSQLDB DB를 생성하는 Hibernate3 플러그인의 hbm2ddl goal을 호출할 필요가 있다. simple-command 디렉토리의 다음 명령을 실행함으로써 수행한다.

$ mvn hibernate3:hbm2ddl
[INFO] Scanning for projects...
[INFO] Searching repository for plugin with prefix: 'hibernate3'.
[INFO] org.codehaus.mojo: checking for updates from central
[INFO] ------------------------------------------------------------------------
[INFO] Building Chapter 7 Simple Command Line Tool
[INFO]    task-segment: [hibernate3:hbm2ddl]
[INFO] ------------------------------------------------------------------------
[INFO] Preparing hibernate3:hbm2ddl
...
10:24:56,151  INFO org.hibernate.tool.hbm2ddl.SchemaExport - export complete
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------

일단 이렇게 실행되면, simple-command 밑에 data/ 디렉토리를 볼 수 있다. 이 data/ 디렉토리는 HSQLDB DB를 포함한다. command-line 날씨 정보를 실행하려면, simple-command 프로젝트 디렉토리에서 다음을 실행한다.

$ java -cp target/simple-command-jar-with-dependencies.jar \
           org.sonatype.mavenbook.weather.Main 60202
2321 INFO  YahooRetriever  - Retrieving Weather Data
2489 INFO  YahooParser  - Creating XML Reader
2581 INFO  YahooParser  - Parsing XML Response
2875 INFO  WeatherFormatter  - Formatting Weather Data
****************************************
Current Weather Conditions for:
  Evanston,
  IL,
  US
****************************************
 
 * Temperature: 75
 * Condition: Partly Cloudy
 * Humidity: 64
 * Wind Chill: 75
 * Date: Wed Aug 06 09:35:30 CDT 2008

이력 조회를 실행하려면, 다음의 명령을 실행한다.

$ java -cp target/simple-command-jar-with-dependencies.jar \
           org.sonatype.mavenbook.weather.Main 60202 history
2470 INFO  WeatherFormatter  - Formatting History Data
Weather History for:
Evanston, IL, US
 
****************************************
 * Temperature: 39
 * Condition: Heavy Rain
 * Humidity: 93
 * Wind Chill: 36
 * Date: 2007-12-02 13:45:27.187
****************************************
 * Temperature: 75
 * Condition: Partly Cloudy
 * Humidity: 64
 * Wind Chill: 75
 * Date: 2008-08-06 09:24:11.725
****************************************
 * Temperature: 75
 * Condition: Partly Cloudy
 * Humidity: 64
 * Wind Chill: 75
 * Date: 2008-08-06 09:27:28.475


7.10. 결론

이곳까지 다다르기 위해서 메이븐과 직접적으로 관련이 없는 내용에 대해 많은 시간을 할해했다. 실세계 시스템을 구현하는데 사용될 수 있는 완전하고 의미있는 예제 프로젝트를 제시함으로써 위와 같이 설명했다. 멋들어지고, 규격화된 결과를 빨리 얻으려고 빠른 길을 선택하지 않았으며, Ruby on Rails 풍의 기술을 사용해서 현혹하고 “쉬운 10분!”에 완성된 자바 엔터프라이즈 어플리케이션을 만들수 있다는 믿음을 부여하지 않았다. 시장에는 이러한 내용들이 많이 있다. 시간이나 노력을 거의 투자하지 않는 가장 쉬운 프레임워크를 판매하려는 사람들이 많이 있다. 이 장에서 하려고 했던 것은 전체 그림, 즉 다중 모듈 빌드에 대한 전체 생태계를 제시하는 것이다. 아파치 Ant를 비방하고 아파치 메이븐을 적용하는데 설득하는 빠르고, 10분의 설명을 붙이는 것이 아닌 생생하게 살아있는 것을 보여주는 것과 유사한 어플리케이션의 영역에서 메이븐을 보여주었다.
메이븐과 관련된 내용에 대해 궁금한 것을 이장을 통해 해결되었다면 성공한 것이다. 유명한 프레임워크들을 사용해서 복잡한 프로젝트를 제시했고, 선언적인 빌드를 사용해서 같이 묶었다. 이장의 60% 이상이 Spring과 Hibernate를 설명하는 것으로 채워졌다는 사실은 대부분이 메이븐은 별도로 떨어져 있다는 것이다. 그 자체로 작동했다. 빌드 프로세스가 아니라 어플리케이션 그 자체에 중점을 두도록 했다. 메이븐을 설명하는데 시간을 사용하고, Spring과 Hibernate와 통합된 “빌드를 빌드” 해야만 했던 작업 대신에 이와 같이 만들어진 프로젝트에서 사용된 기술에 대해 거의 오로지 설명했다. 메이븐을 사용하기 시작하고, 배우려는 시간을 가지려면, 진정으로 절차적인 빌드 스크립트를 작성하는데 시간을 보낼 필요가 없다는 사실로부터 이점을 얻기 시작할 것이다. 빌드에 대한 현실적인 부분에 대해 걱정하는데 시간을 보낼 필요가 없다.
이 장에서 소개한 개략적인 프로젝트를 여러분 자신의 기반으로 사용할 수도 있으며, 그렇게 한다면 필요한 만큼 더 복잡한 모듈을 만드는 기회로 삼을 수 있다. 예를 들어, 이 장에서의 프로젝트는 두개의 서로 다른 모델 프로젝트와, 서로 다른 DB에 저장하는 두개의 저장 프로젝트, 몇 개의 웹 어플리케이션, 자바 모바일 어플리케이션을 가지게 된다. 전반적으로 실세계 시스템은 최소한 15개의 서로 관련된 모듈로 구성된다. 요점은 이 장에서 포함될 가장 복잡한 다중 모듈을 보았다는 것이지만, 이 장의 예제는 단지 메이븐에서 가능한 것의 표면만을 살펴보았다는 것을 역시 알아야 한다.

7.10.1. 프로젝트 인터페이싱에 대한 프로그래밍

이 장에서 6장에 나타난 간단한 예제보다 더 복잡한 다중 모듈 프로젝트를 살펴보았지만, 여전히 실세계 프로젝트에 비해 단순하다. 더 큰 프로젝트에서 그림 7-5와 유사한 시스템을 빌드하는 것을 발견할 수도 있다.

예제 7-5. 크고 복잡한 시스템에 대한 예제



<<Pre                                                                                                                                                          Next>>
저작자 표시 비영리 동일 조건 변경 허락
신고

+ Recent posts

티스토리 툴바