이번 포스팅에서는 빌드 도구(Build Tool)중 하나인 Maven에 대해서 알아보도록 하겠습니다. 그동안 Gradle만 사용해보았고, 정확히 무엇인지 알고 사용했다기보다 그저 복붙을 통한 사용만 해봤기 때문에 빌드 도구에 대한 개념을 정리하고자 작성하게 되었습니다.
빌드(Build) ?
먼저, Maven에 대해 알아보기 앞서 빌드에 대해 알아보겠습니다.
빌드는 소스코드 파일을 컴퓨터에서 실행할 수 있는 독립 소프트웨어 가공물로 변환하는 과정인데 쉽게 풀어 설명하자면, 우리가 작성한 소스코드(Java) 프로젝트에서 쓰인 각각의 파일 자원 (.xml, .jpg, .jar, .properties)을 JVM이나 톰캣같은 WAS가 인식할 수 있는 구조로 패키징 하는 과정 및 결과물이라고 할 수 있다.
빌드 도구 (Build Tool) ?
빌드 도구는 프로젝트 생성, 테스트 빌드, 배포 등의 작업을 위한 전용 프로그램입니다. 빠른 기간동안 계속해서 늘어나는 라이브러리 추가, 프로젝트를 진행하며 라이브러리 버전 동기화의 어려움을 해소하기 위해 등장했고 초기의 Java 빌드 도구로 Ant를 많이 사용하였으나 최근 많은 빌드 도구들이 생겨나 Maven이 많이 쓰였고 , 현재는 Gradle이 많이 쓰인다.
Maven의 정의 및 특징
- Maven은 자바용 프로젝트 관리 도구로 Apache Ant의 대안으로 만들어졌다.
- 프로젝트 전체적인 라이프 사이클을 관리하는 도구이다.
- 필요한 라이브러리를 특정 문서(pom.xml)에 정의한다.
- 정의된 라이브러리와 해당 라이브러리가 동작하는데 필요한 라이브러리까지 관리해 네트워크를 통해 자동으로 다운 받아준다.
- 간단한 설정을 통해 배포 관리가 가능하다.
Maven LifeCycle
✅ Default(Build) : 일반적인 빌드 프로세스를 위한 모델이다.
✅ Clean : 빌드 시 생성되었던 파일들을 삭제하는 단계
✅ Validate : 프로젝트가 올바른지 검증하고 필요한 모든 정보를 사용할 수 있는지 확인하는 단계
✅ Compilie : 프로젝트의 소스코드를 컴파일 하는 단계
✅ Test : 유닛(단위)테스트를 수항해는 단계
✅ Package : 실제 컴파일된 소스코드와 리소스를 jar, war 등의 배포를 위한 패키지로 만드는 단계
✅ Verify : 통합 테스트 결과에 대한 검사를 실행하여 품질 기준을 충족하는지 확인하는 단계
✅ Install : 패키지를 로컬 저장소에 설치하는 단계
✅ Site : 프로젝트 문서와 사이트 작성, 생성하는 단계
✅ Deploy : 만들어진 package를 원격 저장소에 release 하는 단계
최종 빌드 순서는 compile → test → package 이다.
- complie : src/main/java 디렉토리 아래의 모든 소스 코드 컴파일
- test : src/test/java, src/test/resources 테스트 자원 복사 및 테스트 소스 코드 컴파일
- packaging : 컴파일과 테스트가 완료된 후, jar나 war 같은 형태로 압축하는 작업
Maven 설정 파일
프로젝트 객체 모델 (POM - Project Object Model)
POM은 pom.xml 파일을 말하며 pom.xml은 메이븐을 이용하는 프로젝트의 root에 존재하는 xml 파일이다. Maven의 기능을 이용하기 위해 POM이 사용되며, 파일은 프로젝트마다 1개로 pom.xml 파일만 보면 프로젝트의 모든 설정이나 의존성 등을 파악할 수 있다. 다른 이름으로도 파일명을 변경할 수 있지만 pom.xml로 사용하기를 권장한다고 한다.
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.zerozae</groupId>
<artifactId>exam</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>kdt</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
엘리먼트 살펴보기
✅ modelVersion : POM Model의 버전
✅ parent : 프로젝트의 계층 정보
✅ groupId : 프로젝트를 생성하는 조직의 고유 아이디를 결정, 일반적으로 도메인 이름을 거꾸로 적음
✅ artifactId : 프로젝트 빌드시 파일 대표이름으로 groupId 내에서 유일해야한다.
→ artifactid-version.packaging, 위 예의 경우 빌드할 경우 exam-0.0.1-SNAPSHOT.jar 파일이 생성
✅ version : 프로젝트 현재 버전
✅ packaging : 패키징 유형 (jar, war, ear 등)
✅name : 프로젝트 이름
✅ description : 프로젝트에 대한 간략한 설명
✅ url : 프로젝트에 대한 참고 Reference 사이트
✅ properties : 버전 관리시 용이
✅ dependencies : 프로젝트와 의존 관계에 있는 라이브러리들을 관리
✅ build : 빌드에 사용할 플러그인 목록
→ clean , test , package 결과
전이 의존성 (Transitive Dependency) ?
어떤 아티팩트를 의존성으로 추가하면, 그 아티팩트가 가지고 있는 의존성이 함께 딸려오는 경우가 생기는데, 그렇게 '딸려온' 의존성을 Transitive Dependency 라고 합니다.
[ 의존 관계 트리 ]
MyProject
--> A
--> X
위의 의존 관계 트리를 살펴보면 MyProject 가 A를 의존하고 있고 A가 X를 의존하고 있기 때문에 MyProject --> X의 의존 관계가 발생합니다.
(참고) 의존 관계 디버그하기
mvn dependency:tree
[INFO] org.prgms:exame:jar:0.0.1-SNAPSHOT
[INFO] +- org.springframework.boot:spring-boot-starter:jar:3.1.4:compile
[INFO] | +- org.springframework.boot:spring-boot:jar:3.1.4:compile
[INFO] | | \- org.springframework:spring-context:jar:6.0.12:compile
[INFO] | | +- org.springframework:spring-aop:jar:6.0.12:compile
[INFO] | | +- org.springframework:spring-beans:jar:6.0.12:compile
[INFO] | | \- org.springframework:spring-expression:jar:6.0.12:compile
...
Q. 그렇다면, 의존 관계 트리에 대해 한 아티팩트의 여러 버전이 있으면 어떤 버전이 선택될까요 ?
A. 결론은 가장 가까운 정의가 선택됩니다.
// 깊이가 다른 경우
MyProject
--> A
--> X 1.0
--> B
--> C
--> X 2.0
// 깊이가 같은 경우
MyProject
--> A
--> X 1.0
--> B
--> X 2.0
위 트리에서 깊이가 다른 경우 MyProject를 기준으로 X 1.0이 X 2.0보다 가까이 있으므로 X 1.0이 선택됩니다. 깊이가 같은 경우엔 먼저 선언된 X 1.0이 선택됩니다.
Maven Scope
메이븐(Maven)을 사용하게 되면 POM 파일을 다루게 된다고 했죠 ? POM은 pom.xml 파일을 통해 dependency 라고 부르는 의존성 설정을 하는데, 각 dependency는 scope를 가지고 있습니다. 여기서 scope는 해당 dependency가 포함되는 범위에 대한 타입이라고 말할 수 있어요.
[ 종류 ]
1. compile
<dependency>
<groupId>org.zerozae</groupId>
<artifactId>exam</artifactId>
<version>2.5.6</version>
</dependency>
compile은 기본 scope를 생략하면 적용됩니다. 프로젝트의 컴파일, 테스트, 실행에 라이브러리가 필요할 때 사용합니다.
2. provided
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
compile과 유사하지만, 다른 외부 컨테이너에서 제공되는 API인 경우 provided로 지정 시 마지막 패키징할 때 포함되지 않습니다. 예를 들어 tomcat에서 기본적으로 servlet API를 제공하기 때문에 servlet API를 provided로 지정하면 패키징시 제외됩니다.
3. runtime
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
<scope>runtime</scope>
</dependency>
의존관계가 컴파일 시 필요하지 않지만 실행 시 필요함을 의미합니다. 실행 시와 테스트 클래스패스에 속하지만 컴파일 클래스패스에는 속하지 않습니다.
4. test
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
일반적인 어플리케이션 사용에 대해서는 의존관계가 필요없고, 테스트 컴파일과 실행 시점에만 사용됩니다.
5. System
<dependency>
<groupId>com.baeldung</groupId>
<artifactId>custom-dependency</artifactId>
<version>1.3.2</version>
<scope>system</scope>
<systemPath>${project.basedir}/libs/custom-dependency-1.3.2.jar</system.Path>
</dependency>
명시적으로 해당 Jar를 포함하는 것이 제공되어야 한다는 것을 제외하고 provided와 유사합니다. artifact는 항상 사용 가능하며 레포지토리에서 검색하지 않습니다. 또한 <systemPath> ... </systemPath> 엘리먼트를 이용해 jar 파일의 위치를 지정해주어야 합니다.
< 참고 자료 >