Java

메이븐

AlbertIm 2024. 12. 30. 15:51

시작

메이븐은 자바 프로젝트의 빌드, 테스트, 배포를 자동화해 주는 빌드 도구입니다. 여기서 간단히 정리하고자 합니다.

본문

메이븐의 빌드 라이프 사이클

메이븐은 기본적인 빌드 라이프 사이클을 가지고 있습니다. 이 라이프 사이클은 단계(phase)라고 불리며 각 단계는 특정 작업을 수행합니다.

  • validate: 프로젝트 구성이 올바른지 확인하고 빌드할 수 있는지 검증합니다.
  • compile: 프로젝트의 소스 코드를 컴파일합니다.
  • test: 단위 테스트를 수행합니다.
  • package: 컴파일된 코드를 패키징 합니다.
  • verify: 통합 테스트 결과를 확인합니다.
  • install: 패키지를 로컬 저장소에 설치합니다.
  • deploy: 패키지를 원격 저장소에 배포합니다.

또한 클린 라이프 사이클(clean)과 사이트 라이프 사이클(site)이 있습니다. 클린 라이프 사이클은 빌드 시 생성된 파일을 삭제하는 작업을 수행하고 사이트 라이프 사이클은 프로젝트 문서를 생성하는 작업을 수행합니다.

명령 및 POM 소개

메이븐은 mvn 명령어를 통해 빌드를 수행합니다. 또한 메이븐은 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!--  GAV(Group, Artifact, Version)는 프로젝트를 식별하는 정보입니다.-->
    <groupId>me.albert</groupId>
    <artifactId>testmvn</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

</project>

 

메이븐은 일반적으로 동일한 패키지 계층구조를 가지는 소스 코드를 컴파일하고 패키징 합니다. 따라서 소스 코드는 src/main/java 디렉터리에 위치하고 테스트 코드는 src/test/java 디렉터리에 위치합니다.

# 디렉토리 구조
example
├── src
│   ├── main
│   │   └── java
│   │       └── me
│   │           └── albert
│   │               └── Main.java
│   └── test
│       └── java
│           └── me
│               └── albert
│                   └── MainTest.java
└── pom.xml

빌드

mvn compile 명령어를 통해 소스 코드를 컴파일할 수 있습니다.

$ mvn compile

# 결과
[INFO] Scanning for projects...
[INFO] 
[INFO] -------------------------< me.albert:testmvn >--------------------------
[INFO] Building testmvn 1.0-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
Downloading from central: ...
[INFO] 
[INFO] --- resources:3.3.1:resources (default-resources) @ testmvn ---
[INFO] Copying 0 resource from src/main/resources to target/classes
[INFO] 
[INFO] --- compiler:3.13.0:compile (default-compile) @ testmvn ---
Downloading from central: ...
[INFO] Recompiling the module because of changed source code.
[INFO] Compiling 1 source file with javac [debug target 11] to target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.920 s
[INFO] Finished at: 2024-12-30T09:35:48+09:00

메이븐은 기본족으로 target 디렉터리에 컴파일된 클래스 파일을 저장합니다. target/classes 디렉터리에 컴파일된 클래스 파일을 찾을 수 있습니다. 확인하시면 main 디렉터리 아래의 코드만 빌드되었음을 확인할 수 있습니다. 테스트를 컴파일하려면 mvn test-compile 명령어를 사용하면 됩니다.

 

mvn package 명령어를 통해 패키징 할 수도 있습니다. 패키징은 컴파일된 클래스 파일을 JAR, WAR, EAR 등의 형태로 패키징 하는 작업을 수행합니다.

$ mvn package

# 결과
testmvn on  main [!+] is 󰏗 v1.0-SNAPSHOT via  v11.0.18 via  v3.1.3 took 2s 
❯ mvn package
[INFO] Scanning for projects...
[INFO] 
[INFO] -------------------------< me.albert:testmvn >--------------------------
[INFO] Building testmvn 1.0-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
Downloading from central: ...
[INFO] 
[INFO] --- resources:3.3.1:resources (default-resources) @ testmvn ---
[INFO] Copying 0 resource from src/main/resources to target/classes
[INFO] 
[INFO] --- compiler:3.13.0:compile (default-compile) @ testmvn ---
[INFO] Nothing to compile - all classes are up to date.
[INFO] 
[INFO] --- resources:3.3.1:testResources (default-testResources) @ testmvn ---
[INFO] skip non existing resourceDirectory /Users/Albert/Documents/study/testmvn/src/test/resources
[INFO] 
[INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ testmvn ---
[INFO] Nothing to compile - all classes are up to date.
[INFO] 
[INFO] --- surefire:3.2.5:test (default-test) @ testmvn ---
Downloading from central: ...
[INFO] No tests to run.
[INFO] 
[INFO] --- jar:3.4.1:jar (default-jar) @ testmvn ---
Downloading from central: ...
[INFO] Building jar: /Users/Albert/Documents/study/testmvn/target/testmvn-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.440 s
[INFO] Finished at: 2024-12-30T09:44:41+09:00
[INFO] ------------------------------------------------------------------------

mvn package 명령어를 실행하면 target 디렉터리에 JAR 파일이 생성됩니다. JAR 파일은 target 디렉터리에 testmvn-1.0-SNAPSHOT.jar 파일로 생성됩니다.

 

다른 라이프 사이클을 실행하려면 mvn 명령어 뒤에 라이프 사이클 이름을 입력하면 됩니다. 예를 들어 mvn clean 명령어를 실행하면 클린 라이프 사이클이 실행됩니다.

manifest 조작하기

메이븐은 JAR 파일의 매니페스트를 조작할 수 있습니다. 매니페스트는 생성한 JAR 파일은 JVM에게 해당 JAR 파일이 어떻게 실행되어야 하는지 알려주는 정보를 담고 있습니다. 예를 들어 Main-Class속성을 추가하여 JAR 파일을 실행할 때 메인 클래스를 지정할 수 있습니다.

<?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 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>me.albert</groupId>
    <artifactId>testmvn</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--  GAV(Group, Artifact, Version)는 프로젝트를 식별하는 정보입니다.-->
    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <!-- Maven Jar Plugin은 프로젝트를 JAR 파일로 패키징하는데 사용됩니다. -->
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.3.0</version>
                <!-- configuration 태그를 사용하여 메니페스트를 조작할 수 있습니다. -->
                <configuration>
                    <archive>
                        <manifest>
                            <!-- JAR 파일이 실행될 때 클래스패스를 추가합니다. -->
                            <addClasspath>true</addClasspath>
                            <!-- Main-Class는 JAR 파일이 실행될 때 시작점이 되는 클래스를 지정합니다. -->
                            <mainClass>me.albert.Main</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

다른 언어 추가하기

메이븐은 다른 언어를 추가할 수 있습니다. 예를 들어 Kotlin을 추가하려면 kotlin-maven-plugin 플러그인을 추가하면 됩니다. 다음은 Kotlin을 추가한 예시입니다.

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>me.albert</groupId>
    <artifactId>testmvn</artifactId>
    <version>1.0-SNAPSHOT</version>


    <properties>
        <!-- 코틀린 버전을 지정 -->
        <kotlin.version>2.1.0</kotlin.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- 코틀린 라이브러리를 추가 -->
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-stdlib</artifactId>
            <version>2.1.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.jetbrains.kotlin</groupId>
                <artifactId>kotlin-maven-plugin</artifactId>
                <version>${kotlin.version}</version>
                <extensions>true</extensions>

                <executions>
                    <!-- 컴파일 단계에 코틀린 소스코드를 컴파일 -->
                    <execution>
                        <id>compile</id>
                        <phase>process-sources</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                        <configuration>
                            <sourceDirs>
                                <!-- 코틀린 소스코드가 있는 디렉토리를 지정 -->
                                <sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
                                <!-- 자바 소스코드가 있는 디렉토리를 지정 -->
                                <sourceDir>${project.basedir}/src/main/java</sourceDir>
                            </sourceDirs>
                        </configuration>
                    </execution>

                    <!-- 테스트 컴파일 단계에 코틀린 소스코드를 컴파일 -->
                    <execution>
                        <id>test-compile</id>
                        <goals>
                            <goal>test-compile</goal>
                        </goals>
                        <configuration>
                            <sourceDirs>
                                <!-- 코틀린 테스트 소스코드가 있는 디렉토리를 지정 -->
                                <sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
                                <!-- 자바 테스트 소스코드가 있는 디렉토리를 지정 -->
                                <sourceDir>${project.basedir}/src/test/java</sourceDir>
                            </sourceDirs>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <executions>
                    <!-- 기본 컴파일러 플러그인을 무효화 -->
                    <execution>
                        <id>default-compile</id>
                        <phase>none</phase>
                    </execution>
                    <!-- 기본 테스트 컴파일러 플러그인을 무효화 -->
                    <execution>
                        <id>default-testCompile</id>
                        <phase>none</phase>
                    </execution>
                    <!-- 컴파일러 플러그인을 컴파일 단계에 추가 -->
                    <execution>
                        <id>java-compile</id>
                        <phase>compile</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                    <!-- 테스트 컴파일러 플러그인을 테스트 컴파일 단계에 추가 -->
                    <execution>
                        <id>java-test-compile</id>
                        <phase>test-compile</phase>
                        <goals>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

테스트

메이븐은 JUnit, TestNG 등의 테스트 프레임워크를 지원합니다. 테스트를 실행하려면 위존성을 추가하고 mvn test 명령어를 실행하면 됩니다.

의존성은 다음과 같이 추가할 수 있습니다.

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>me.albert</groupId>
    <artifactId>testmvn</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- JUnit 5 의존성 추가 -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.11.4</version>
            <scope>test</scope> <!-- 테스트 스코프로 지정 -->
        </dependency>
    </dependencies>
</project>
$ mvn test

# 결과
[INFO] Scanning for projects...
[INFO] 
[INFO] -------------------------< me.albert:testmvn >--------------------------
[INFO] Building testmvn 1.0-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- resources:3.3.1:resources (default-resources) @ testmvn ---
[INFO] Copying 0 resource from src/main/resources to target/classes
[INFO] 
[INFO] --- compiler:3.13.0:compile (default-compile) @ testmvn ---
[INFO] Recompiling the module because of added or removed source files.
[INFO] Compiling 1 source file with javac [debug target 11] to target/classes
[INFO] 
[INFO] --- resources:3.3.1:testResources (default-testResources) @ testmvn ---
[INFO] skip non existing resourceDirectory /Users/Albert/Documents/study/testmvn/src/test/resources
[INFO] 
[INFO] --- compiler:3.13.0:testCompile (default-testCompile) @ testmvn ---
[INFO] Recompiling the module because of changed dependency.
[INFO] 
[INFO] --- surefire:3.2.5:test (default-test) @ testmvn ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  0.743 s
[INFO] Finished at: 2024-12-30T14:26:58+09:00
[INFO] ------------------------------------------------------------------------

의존성 관리

메이븐은 pom.xml파일을 통해 의존성 관리를 할 수 있습니다. 또한 메이븐은 라이브러리를 위한 중앙 저장소를 제공합니다.

중앙 저장소의 링크는 다음과 같습니다.

의존성을 추가하려면 pom.xml 파일에 의존성을 추가하면 됩니다.


<dependencies>
    <!-- JUnit 5 의존성 추가 -->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.11.4</version>
        <scope>test</scope> <!-- 테스트 스코프로 지정 -->
    </dependency>
</dependencies>

의존성을 추가하면 메이븐은 의존성을 다운로드하고 프로젝트에 추가합니다.

 

의존성이 충돌할 경우 mvn dependency:tree 명령어를 사용하여 의존성 트리를 확인하고 충돌을 확인하고 pom.xml 파일을 수정하여 충돌을 해결할 수 있습니다.

$ mvn dependency:tree
<!-- 충돌 해결 -->
<dependencies>
    <dependency>
        <groupId>use.old.example</groupId>
        <artifactId>example</artifactId>
        <version>1.0</version>
        <!-- 충돌 해결을 위해 특정 의존성을 제외할 수 있습니다. -->
        <exclusions>
            <exclusion>
                <groupId>new.example</groupId>
                <artifactId>example</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>new.example</groupId>
        <artifactId>example</artifactId>
        <version>2.0</version>
    </dependency>
</dependencies>

테스트 커버리지 측정

JaCoCo는 자바 코드의 테스트 커버리지를 측정하는 라이브러리입니다. JaCoCo를 사용하면 코드의 테스트 커버리지를 측정할 수 있습니다.

JaCoCo를 사용하려면 pom.xml 파일에 플러그인을 추가하면 됩니다.


<build>
    <plugins>
        <plugin>
            <groupId>org.jacoco</groupId>
            <artifactId>jacoco-maven-plugin</artifactId>
            <version>0.8.7</version>
            <executions>
                <!-- prepare-agent 목표를 실행하여 JaCoCo 에이전트를 준비합니다. -->
                <execution>
                    <goals>
                        <goal>prepare-agent</goal>
                    </goals>
                </execution>
                <!-- report 목표를 실행하여 테스트 커버리지 보고서를 생성합니다. -->
                <execution>
                    <id>report</id>
                    <phase>test</phase>
                    <goals>
                        <goal>report</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

기타

  • 메이븐은 다중 릴리스 JAR 파일을 지원합니다. 다중 릴리스 JAR 파일은 자바 9부터 지원되는 기능으로 자바 9 이상에서만 사용할 수 있습니다.
  • 메이븐은 다중 모듈 프로젝트를 지원합니다. 다중 모듈 프로젝트는 여러 모듈을 하나의 프로젝트로 관리하는 기능입니다.
  • 메이븐은 다양한 플러그인을 지원합니다. 플러그인은 메이븐의 빌드 프로세스를 확장하는 기능입니다.

마무리

메이븐(Maven)은 자바 프로젝트의 빌드, 테스트, 배포 과정을 자동화해 주는 빌드 도구입니다. 이를 활용하면 프로젝트의 빌드 프로세스를 효율적이고 체계적으로 관리할 수 있습니다.

최근에는 Gradle이 점점 더 많이 사용되는 추세입니다. Gradle은 메이븐보다 강력한 기능을 제공하며 빌드 스크립트를 Groovy 또는 Kotlin으로 작성할 수 있습니다. 또한, 메이븐에 비해 코드가 간결하고 가독성이 높아 개발자들에게 선호되고 있습니다.

그럼에도 불구하고 메이븐은 여전히 다양한 프로젝트에서 활발히 사용되고 있으므로 그 기본 개념을 간단히 정리해 보았습니다.