옛날에 애마좐(Amazon) 유역에 사는 '치카마카'라는 거미의 거미줄로 실을 짜서 쫄티를 만들었다는 분이 있었는데 그걸 보면서 정말 웃겨 죽는 줄 알았던 기억이 납니다. 생각난 김에 동영상을 추가해둘까요? ㅋㅋ

13분 정도 되는 동영상을 잼난 부분만 편집한 동영상인데도 8분이나 합니다.ㅋㅋ

Maven의 폼(pom.xml)관리를 하다보면, 겪게 되는 의존성 헬.. 의존성 헬을 해결하려고 exclusion을 하다보면 발생하는 XML 헬.. 지옥의 연속을 겪게 됩니다. 그런데, 오늘 사부님과 M2Eclipse 플러긴 얘기를 하다가 화장실에 갔을 때 갑자기 exclusion을 하지 않아도 되는 아이디어가 떠올랐습니다. 제가 왜 그동안 exclusion을 했는지 모르겠더군요.

먼저 메이븐 폼 관리를 하다보면 겪게 되는 의존성 헬이란, A-> B, C->B 이런 참조 구조에서 A와 C가 참조하는 B 라이브러리의 버전이 다를 경우에 발생하는 문제인데요. 이 문제를 헬이라고 표현하는 이유는 저런 참조 구조를 발견하는게 쉽지 않기 때문입니다. 얽히고 얽혀있는 라이브러리들 간의 의존 관계에서 저러한 참조 관계를 찾아 낸다는 것은 참으로 피곤한 일 중에 하나 일 겁니다. 그래서 pom.xml에 의존성을 추가할 때 같이 딸려오는 의존성들을 일일히 확인하면서 추가할 필요가 있습니다.

Maven을 쓴다고 해서 종속성을 안중에서 Out 시킬 수 있느냐?  (2)

저 같은 경우 일일히 추가하는 라이브러리들의 pom.xml을 열어서 그 안에서 어떤 라이브러리를 어떤 버전을 사용하는지 보고, 스프링을 기준으로 그 보다 하위 버전을 가져올 경우에는 전부 exclusion을 시켰었습니다. 그랬더니, 수 많은 exclusion 설정(특히 하이버네이트 추가할 때)으로 pom.xml이 상당히 길어지고, 가독성도 떨어졌습니다. 그래서 pom.xml에 정의되어 있는 의존성들을 별도의 pom으로 각각 빼내고, spring.pom와 hiber.pom을 만들어서 그 안에 정의한 의존성들을 하나의 superpom.xml에서 참조하도록 설정을 했습니다.

Managed dependency는 무슨 뜻인가요?  (0)
Managed Depedency 네 녀석의 정체를 드디어 알았다.  (0)
기본적인 managed dependency 사용법  (2)

이렇게 하니까 superpom.xml은 좀 간결해 졌지만, 의존성 정보를 분리한다는 것이 역시 쉽지 않은 일이었습니다. 어떤 라이브러리들에 대한 의존성을 spring.pom에 정의하고 어떤 라이브러리들을 hiber.pom에 정의할 지 분명하지가 않으니까요. 그래서 요즘은 다시 의존성 정보들을 다시 superpom.xml로 가져올까도 생각 중입니다.

그전에 우선 정리를 좀 해야겠죠.

그 정리 작업 중에 하나가 바로 불필요한 exclusion 설정을 없애는 겁니다. A라는 프로젝트가 있고 이 프로젝트의 의존성이 다음과 같다고 가정하겠습니다.

A -> B -> C(1.0)
A -> D -> E -> C(2.0)

http://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html

먼저, Transitive Dependency 정책에 따라 A 프로젝트의 pom.xml에 B와 D에 대한 의존성만 추가해도, E와 C 라이브러리를 가져오게 될 겁니다. 그런데 문제는 C 라이브러리를 B도 참조하고 E도 참조하는데 서로 참조하는 버전이 다르다는 겁니다.

이럴 경우 위 링크의 Transitive Dependency에 있는 Dependency mediation 규칙에 따라 "가장 가까운" 의존성을 사용하게 됩니다. 따라서 C(1.0)을 사용하게 되는거죠. 그런데 만약 같은 뎁쓰에 있다면 어떻게 될까요?

A -> B -> C(1.0)
A -> D -> C(2.0)

이 경우에는 메이븐의 버전에 따라 좀 다릅니다. 2.0.4에는 저런 경우에 어떻게 해야 할지 정의해둔 뭔가가 없습니다. 그런데 2.0.5 부터는 "먼저 선언된 순서"에 따라서 A 프로젝트의 pom에 B에 대한 의존성을 먼저 선언했다면 C(1.0)을 참조하게 될 겁니다.

자 저런 상황에서 C(1.0)이 아니라 C(2.0)을 사용하려면 어떻게 해야 할까요?

저는 exclusion을 사용해서, B에 대한 의존성을 정의할 때 다음과 같이 선었했었습니다.

        <dependency>
            <groupId>note.whiteship</groupId>
            <artifactId>B</artifactId>
            <version>1.0</version>
            <exclusions>
                <exclusion>
                    <groupId>note.whiteship</groupId>
                    <artifactId>C</artifactId>
                </exclusion>
             </exclusions>
        </dependency>

참으로 번거롭고, XML 설정할 것이 참 많았습니다. 그런데 문제는 잘 생각해보면 정말 쉽게 해결할 수 있는 거였습니다.

A -> C(2.0)

이렇게 의존성을 하나 추가해주기만 하면 끝나는 거였죠. 그렇게 되면

A -> B -> C(1.0)
A -> D -> E -> C(2.0)
A -> C(2.0)

또는

A -> B -> C(1.0)
A -> D -> C(2.0)
A -> C(2.0)

이렇게 전체 설정이 될테고 그럼 "가장 가까운" 녀석이 우선시 되기 때문에 C(2.0)을 쓰고 나머진 무시하게 될테니까요. 설정에서도 exclusion을 사용할 필요 없이 그냥 A의 pom.xml에 하나의 <dependency></dependency> 덩어리를 추가해주기만 하면 됩니다.

        <dependency>
           <groupId>note.whiteship</groupId>
           <artifactId>C</artifactId>
           <version>2.0</version>
        </dependency>

이렇게요. 흠~ 괜찮지 않나요. 그래도 exclusion을 해야 할까요?