profile image

L o a d i n g . . .

article thumbnail image
Published 2022. 11. 10. 08:25
728x90

이번 포스팅을 통해 Maven에 대해 더 자세히 이해해보도록 하겠습니다. 저 포함 많은 분들께서는 Maven을 단순히 의존성을 끌어와 라이브러리를 활용하는 정도로만 사용하고 있는데요, Maven의 동작원리를 안다면 이를 더 효과적으로 사용하고 의존성 문제가 생겼을 때 쉽게 해결할 수 있습니다. 

POM.xml 구조 

POM(Project Object Model)은 Maven이 해당 프로젝트를 어떻게 빌드할 것인지 명시하기 위해 사용되는 XML 파일입니다. 개발자 입장에서 자주 보게 되는 속성(attributes) 위주로 설명드리겠습니다. 

relativePath 

부모 pom.xml을 명시할 때(override) 사용하는 속성입니다. 한 단계 위에 있는 디렉터리가 기본 설정입니다.

modelVersion 

XML schema definition version입니다. Maven 3.x.x를 활용하기 위해서는 modelVersion은 4.0.0으로 설정돼야 합니다. 

Maven Coordinates 

Maven coordinates를 통해 repository(e.g. maven central repository) 해당 Maven 프로젝트를 식별할 수 있습니다. Maven coordinates는 groupId + artifactId + version으로 구성됩니다. 

Maven Coordinates

groupId + artifactId + version의 첫 글자를 모아 GAV라고 부르기도 합니다. 참고로 artifactId와 해당 프로젝트의 base directory의 이름을 동일하게 설정하는 게 권고됩니다. 

version을 사용할 때 주의할 점이 있습니다. 명시된 version에 -SNAPSHOT을 명시하게 되면(e.g. 1.0.0-SNAPSHOT) 해당 프로젝트의 소스코드는 수정될 수 있음을 알리고 저장소(repository)에서 overwrite이 가능합니다. SNAPSHOT이 명시되지 않으면 수정이 불가능한 것으로 간주돼서 해당 프로젝트가 저장소에 올라간 경우 overwrite이 불가능합니다. 

packaging 

프로젝트를 빌드했을 때 결과 artifact type을 지정하기 위해 사용합니다. 예를 들면 jar, war, ear, pom이 존재합니다. 

Packaging

프로젝트 상세 정보 

프로젝트 상세정보를 나타내기 위해 name, description, url 속성이 존재합니다. name은 프로젝트의 이름(주로 짧게 설정합니다), description은 프로젝트에 대한 상세 정보 그리고 url은 해당 프로젝트와 관련된 웹사이트를 명시합니다.

modules 

현재 POM이 Aggregator POM인 경우 child POM을 명시하기 위해서 사용합니다. 

Aggregator POM 
말 그대로 여러 프로젝트를 묶기 위해 사용되는 POM입니다. 해당 POM은 modules 속성에 child POM에 대한 정보를 module 속성으로 관리합니다. 여러 child 프로젝트를 관리하고 빌드 순서를 보장하기 위해 사용합니다. 단, Aggregator POM을 사용한다고 해도 Aggregator POM의 의존성이 child로 상속되지는 않습니다. 

modules

scm (Source Control Management) 

Source Control Management 시스템과 연동할 수 있는 정보를 명시할 수 있습니다. 해당 소스 코드를 릴리스하거나 태그를 설정할 때 유용합니다. 

repositories, pluginRepositories

Maven build artifact와 dependency들을 보관하는 저장소입니다. 이 속성을 사용하면 해당 프로젝트를 빌드하기 위해서는 이 속성에 명시된 저장소에 접근이 가능해야 합니다. 만약 접근이 불가능하다면 빌드가 실패할 수 있습니다. 

issueManagement

Issue tracking system과 연동하기 위한 정보를 명시할 수 있습니다. 

ciManagement 

Continuous Integration과 관련된 정보를 명시할 수 있습니다. 

properties 

해당 POM에서 반복적으로 사용되는 값의 경우 properties에 해당 값을 설정하고 POM에서 ${}을 통해 사용할 수 있습니다. 

properties의 활용

dependencyManagement 

해당 프로젝트에서 사용하는 dependency의 version을 명시하기 위해 사용합니다. dependencyManagement에 특정 dependency의 정보가 설정되면 dependencies에 명시된 dependency의 version을 명시하지 않아도 됩니다. 이름 그대로 dependency를 관리하기 위해 사용되는 속성입니다. 

dependencyManagement

dependencyManagement은 다음과 같은 효과를 가집니다. 
1. dependencyManagement에 명시된 dependency는 version을 명시하지 않아도 됩니다. 
2. dependencyManagement가 명시된 POM을 parent POM으로 설정함으로써 child POM은 손쉽게 의존성을 관리할 수 있습니다. 
3. Maven은 effective POM 생성을 위해서 dependencyManagement를 참조합니다. 
4. dependencyManagement에 명시된 dependency는 참고용입니다. 만약 프로젝트의 dependencies에 해당 dependency가 명시되지 않으면 프로젝트에 의존성으로 추가되지 않습니다. 
5. dependencyManagement에 BOM(Bill Of Materials)를 import scope으로 명시하게 되면 BOM에서 관리하는 의존성 정보를 사용할 수 있습니다. 
BOM (Bill Of Materials)은 함께 잘 동작하는 의존성을 묶은 POM입니다. 예를 들면 armeria-bom이 있는데 armeria-bom의 경우 dependencyManagement 속성에 함께 동작하는 여러 dependencies를 version과 함께 명시하고 있습니다. 
더보기
<!-- armeria-bom 예시 -->
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.linecorp.armeria</groupId>
  <artifactId>armeria-bom</artifactId>
  <version>1.20.2</version>
  <packaging>pom</packaging>
  <name>Armeria (armeria-bom)</name>
  <description>Asynchronous HTTP/2 RPC/REST client/server library built on top of Java 8, Netty, Thrift and gRPC (armeria-bom)</description>
  <url>https://armeria.dev/</url>
  <inceptionYear>2015</inceptionYear>
  <licenses>
    <license>
      <name>The Apache License, Version 2.0</name>
      <url>https://www.apache.org/license/LICENSE-2.0.txt</url>
      <distribution>repo</distribution>
    </license>
  </licenses>
  <developers>
    <developer>
      <name>LINE Corporation</name>
      <email>dl_oss_dev@linecorp.com</email>
      <url>https://engineering.linecorp.com/en/</url>
    </developer>
  </developers>
  <scm>
    <connection>scm:git:https://github.com/line/armeria.git</connection>
    <developerConnection>scm:git:ssh://git@github.com/line/armeria.git</developerConnection>
    <url>https://github.com/line/armeria</url>
  </scm>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-annotation-processor</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-brave</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-bucket4j</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-consul</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-dropwizard1</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-dropwizard2</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-eureka</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-graphql</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-graphql-protocol</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-grpc</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-grpc-protocol</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-javadoc</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-jetty9</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-junit4</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-junit5</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-kafka</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-kotlin</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-logback</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-oauth2</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-protobuf</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-reactor3</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-resteasy</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-retrofit2</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-rxjava2</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-rxjava3</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-saml</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-sangria_2.12</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-sangria_2.13</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-scala_2.12</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-scala_2.13</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-scala_3</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-scalapb_2.12</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-scalapb_2.13</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-scalapb_3</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-spring-boot1-autoconfigure</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-spring-boot1-starter</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-spring-boot2-actuator-autoconfigure</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-spring-boot2-actuator-starter</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-spring-boot2-autoconfigure</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-spring-boot2-starter</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-spring-boot2-webflux-autoconfigure</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-spring-boot2-webflux-starter</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-thrift0.13</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-thrift0.14</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-thrift0.15</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-thrift0.16</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-thrift0.17</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-thrift0.9</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-tomcat8</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-tomcat9</artifactId>
        <version>1.20.2</version>
      </dependency>
      <dependency>
        <groupId>com.linecorp.armeria</groupId>
        <artifactId>armeria-zookeeper3</artifactId>
        <version>1.20.2</version>
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>

dependencies 

실제 사용하고자 하는 dependency를 명시하는 속성입니다. 여기서 가장 중요한 것은 해당 project에 동일한 의존성이지만 서로 다른 버전인 경우 어떻게 가져오는지 이해하는 것입니다. Maven은 중복된 의존성이 존재하는 경우 현재 POM에서 가장 가까우면서도 먼저 명시된 의존성을 가져옵니다.

Maven이 중복된 dependency에서 선택하는 원리

Maven 의존성 하면 transitive dependency라는 용어가 등장합니다. 프로젝트 A의 pom.xml이 프로젝트 B의 pom.xml을 부모로 설정하고, B의 pom.xml이 C라는 프로젝트의 pom.xml을 부모로 설정했을 때 C의 의존성들은 A의 transitive depency라고 합니다. 이러한 transitive dependency를 순회하면서 그리는 게 maven의 dependency tree입니다. 

A --> B --> C
C의 pom.xml에 명시된 의존성은 A의 transitive dependency입니다. 

mvn dependency:tree 커맨드로 확인한 maven dependency tree

이러한 중복된 의존성을 모두 제거한 최종 상태의 POM을 effective POM이라고 합니다. 

maven effective POM - 출처)&nbsp;https://cguntur.me/2020/05/29/understanding-apache-maven-part-4/

Intellij를 통해서 effective POM을 확인하는 방법은 다음과 같습니다. 

Intellij effective POM

dependency 속성 

dependency 속성에는 어떤 속성 값이 있는지 살펴보겠습니다.

첫 번째로 exclusion 속성이 있습니다. 이는 해당 의존성에 함께 달려오는 의존성을 제외시키고자 할 때 사용됩니다. 즉 dependency tree에서 특정 branch를 잘라내는 것과 동일합니다. 

dependency exclusion - 출처) https://cguntur.me/2020/05/29/understanding-apache-maven-part-4/

두 번째는 scope입니다. scope 속성은 해당 의존성이 프로젝트 lifecycle에서 어떻게 동작해야 하는지 명시하기 위해 사용됩니다. 

  • compile: 기본 값으로 프로젝트 classpath에 해당 의존성이 포함됩니다. Transitive dependency로 child에게 상속됩니다. 
  • runtime: compile time에는 필요 없고 오직 runtime에 필요한 의존성을 표시하기 위해 사용됩니다. Transitive dependency로 child에게 상속됩니다. 
  • provided: transitive dependency로 상속되지 않습니다. 
  • test: test시에만 사용되는 의존성입니다. Transitive dependency로 상속되지 않습니다. 
  • system: repository에서 의존성을 찾는 게 아닌 현재 system에서 의존성을 찾습니다. systemPath를 명시해야 합니다. Transitive dependency로 상속되지 않습니다. 
  • import: dependencyManagement 속성에서 사용되는 scope입니다. Transitive dependency로 상속되지 않습니다. 

plugins 

plugin을 이해하기 위해서는 Maven의 lifecycle과 goal을 우선 이해해야 합니다. 

lifecycle 

lifecycle은 여러 단계(phase)로 구성됩니다. 각 단계는 순차적으로 실행되고 특정 단계를 실행하는 것은 이전의 모든 단계를 실행하는 것과 같습니다. 각 단계(phase)는 goal과 연결됩니다. 

maven lifecycle

goal 

각 단계(phase)는 관련된 goals를 순차적으로 실행합니다. 각각의 goal은 특정 작업 단위입니다. 각각의 goal은 관련된 plugin을 통해 실행됩니다. 

plugins and goals

위의 그림에서 clean plugin을 통해 clean, help goal을 실행할 수 있고, deploy plugin을 통해 deploy, deply-file, help goal을 실행할 수 있습니다.

 

마무리 

이번 포스팅을 통해 Maven에 대해 상세히 살펴봤습니다. 매번 프로젝트 Maven을 분석할 때 어떤 의존성이 있는지만 확인하고 넘어갔었는데, 이번 기회를 통해 Maven을 더 잘 이해하고 사용할 수 있게 된 것 같습니다. 

728x90
복사했습니다!