본문 바로가기
빌드노트 검색

빌드노트

Maven exclusion을 넣었는데 같은 라이브러리가 남는다

Maven exclusion은 선언한 dependency를 거치는 경로에 적용된다. 같은 라이브러리를 두 경로로 가져오는 합성 프로젝트에서 한쪽 제외와 양쪽 제외를 비교하고, 남은 경로의 기능 요구를 보고 수정할 위치를 정한다.

Maven의 직접 의존성에 exclusion을 추가했는데 dependency:tree에 같은 라이브러리가 계속 보인다면, 남아 있는 항목의 상위 경로부터 읽는다. exclusion은 그것을 적은 dependency를 거쳐 들어오는 전이 의존성을 제외한다. 다른 dependency가 같은 라이브러리를 가져오면 그 경로는 남는다. Maven 공식 가이드도 exclusion을 특정 의존성에 설정하며, 그 아래 의존 그래프에 적용한다고 설명한다. Maven exclusion의 적용 범위 (새 탭에서 열림)

따라서 같은 exclusion을 원래 위치에 다시 넣기보다, 정확한 좌표와 나머지 도달 경로를 확인해야 한다. 그 라이브러리가 정말 필요 없는지 판단한 뒤 경로별로 제외하거나, 필요한 기능에 맞게 의존 구조를 바꾼다.

남아 있는 라이브러리까지 경로를 따라간다

문제가 나는 소비 모듈의 pom.xml이 있는 디렉터리에서 다음 명령을 실행한다. Windows PowerShell과 Maven이 설치된 환경을 기준으로 했다. lab.exclusion:shared는 아래 실험의 합성 좌표이므로, 자기 프로젝트에서는 제외하려는 라이브러리의 실제 groupId:artifactId로 바꾼다. 파일 이름이 비슷하다는 이유로 같은 artifact라고 판단하지 않는다.

mvn.cmd -B -ntp org.apache.maven.plugins:maven-dependency-plugin:3.11.0:tree "-Dverbose=true" "-Dincludes=lab.exclusion:shared"

다른 셸에서는 mvn.cmd를 mvn으로 바꾼다. 평소 빌드의 프로필·settings·저장소 설정을 동일하게 적용하고, 형제 모듈이 필요하면 그 artifact도 해석 가능한 상태여야 한다. 사용한 Dependency Plugin 3.11.0은 Maven 3.6.3 이상과 JDK 8 이상이 필요하며, 프로젝트 자체가 요구하는 JDK 조건도 만족해야 한다. 플러그인 실행 조건 (새 탭에서 열림)

includes는 대상 라이브러리로 이어지는 경로를 골라 보여 준다. verbose=true는 중복 등으로 생략된 노드도 출력한다. 기본 출력만 보면 두 번째 경로를 놓칠 수 있다. 이 옵션들은 트리 표시를 위한 것이며, 특히 tree goal의 -Dexcludes=...로 줄을 숨겨도 POM의 의존성 제외 설정이 되지는 않는다. tree 옵션 (새 탭에서 열림), 경로 필터 예제 (새 탭에서 열림)

명령이 실패하면 의존성 해석 오류부터 해결한다. 성공했는데 필터 결과가 비었다면 좌표, 소비 모듈, 활성 프로필을 확인하고 "-Dincludes=..." 인자를 빼서 전체 트리도 본다. 빈 필터 결과만으로 원하는 라이브러리를 제거했다고 단정하지 않는다.

한쪽을 제외하자 다른 쪽 경로가 남았다

실제 앱과 분리한 새 프로젝트에서 app, left, right, shared 네 모듈을 만들었다. app은 left와 right에 직접 의존하고, 두 모듈은 모두 shared에 의존한다. 모든 좌표의 groupId는 lab.exclusion, 버전은 1.0이며 scope는 기본값인 compile이다. 버전 충돌 없이 exclusion의 경로 범위만 비교했다.

처음에는 exclusion을 넣지 않았고, 다음에는 app POM의 left dependency에만 shared exclusion을 넣었다. 같은 조회 명령으로 얻은 출력이다. 조건 이름을 붙이고 Maven의 [INFO] 접두사와 빌드 안내를 덜어냈다.

제외 전
lab.exclusion:app:jar:1.0
+- lab.exclusion:left:jar:1.0:compile
|  \- lab.exclusion:shared:jar:1.0:compile
\- lab.exclusion:right:jar:1.0:compile
   \- (lab.exclusion:shared:jar:1.0:compile - omitted for duplicate)

left 경로만 제외한 뒤
lab.exclusion:app:jar:1.0
\- lab.exclusion:right:jar:1.0:compile
   \- lab.exclusion:shared:jar:1.0:compile

처음의 omitted for duplicate는 같은 의존성의 중복 표시를 생략했다는 뜻이다. right → shared 경로가 존재한다는 단서로 읽는다. left 쪽을 제외한 뒤에는 이 경로의 shared가 일반 항목으로 남았다. left 쪽 exclusion이 적용되어도 right 쪽에서 shared를 가져올 수 있었다.

두 번째 출력에 left가 없는 것은 includes에 맞는 경로만 표시했기 때문이다. app의 직접 의존성인 left 선언은 그대로 두었다. 자신의 트리에서도 남은 shared 바로 위의 항목부터 app까지 올라가면, 추가로 검토할 직접 의존성을 찾을 수 있다.

제외가 필요한 각 dependency에서 바꾼다

한쪽 경로를 제외할 때 사용한 설정은 다음과 같다. 소비 앱 POM의 일반 dependencies 안에 있는 기존 left 항목을 이처럼 수정했다. exclusion의 좌표에는 제외할 shared를 적고, version은 적지 않는다. 실제 프로젝트에서는 기존 dependency의 버전·scope 등 필요한 설정을 유지한다.

<dependency>
  <groupId>lab.exclusion</groupId>
  <artifactId>left</artifactId>
  <version>1.0</version>
  <exclusions>
    <exclusion>
      <groupId>lab.exclusion</groupId>
      <artifactId>shared</artifactId>
    </exclusion>
  </exclusions>
</dependency>

이 합성 그래프에서 shared에 이르는 경로를 모두 끊으려면, app의 right dependency 안에도 같은 exclusions 블록을 넣어야 했다. right의 groupId·artifactId·version은 유지했다. 여러 단계 아래의 전이 의존성을 제외하더라도, 내 POM에서 그 경로를 시작하는 dependency에 exclusion을 둘 수 있다. Maven의 exclusion 설정 예제 (새 탭에서 열림)

양쪽을 수정한 뒤에는 shared 필터의 출력이 비었다. 이어 같은 명령에서 "-Dincludes=lab.exclusion:shared"만 빼고 전체 트리를 확인하자 다음 세 줄이 남았다. 두 직접 의존성은 유지되고 그 아래 shared가 사라졌다.

lab.exclusion:app:jar:1.0
+- lab.exclusion:left:jar:1.0:compile
\- lab.exclusion:right:jar:1.0:compile

네 조회는 모두 종료 코드 0이었다. 같은 전용 로컬 저장소를 사용했고, shared JAR 파일은 그 저장소에 계속 있었다. 이 결과에서 바뀐 것은 app이 해석한 의존 경로다. 로컬 저장소의 JAR을 삭제할 필요는 없었다. 또 다른 경로가 나중에 추가되면 shared가 다시 들어올 수 있으므로, 양쪽 exclusion을 전역 금지 설정으로 취급해서는 안 된다.

다만 남은 경로에서 shared를 실제로 사용하는 기능이 있다면, 트리를 비우기 위해 exclusion을 추가하는 것으로 끝내면 안 된다. 그 기능이 필요하면 의존성을 유지할지, 해당 의존이 없는 다른 모듈이나 버전으로 바꿀지 검토한다. 자기 소유 라이브러리라면 공통 기능과 추가 기능의 모듈 분리도 선택지다. 공식 가이드도 exclusion을 마지막 수단으로 설명하며 기능을 나눌 수 있는 경우를 함께 제시한다. exclusion을 선택하는 조건 (새 탭에서 열림)

확인한 환경은 Windows 11, Maven 3.9.16, OpenJDK 17.0.2, Dependency Plugin 3.11.0이다. 합성 JAR 네 개를 새 전용 로컬 저장소에 설치하고, 위 PowerShell 조회도 직접 실행했다. 모듈의 Java 클래스는 표식만 반환하며 서로 호출하지 않는다. 따라서 이번 관찰은 의존성 트리의 변화까지다. 실제 라이브러리를 제외한 뒤의 기능 정상 동작, 최종 배포 JAR의 내용, IDE 실행 환경, 플러그인 자체의 의존성은 검증하지 않았다.

← 목록으로 돌아가기