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

8장. POM 최적화와 리팩토링

by javauser 2009. 10. 6.

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

반응형