빌드 관리도구 - Maven과 Gradle

빌드관리 도구란?

 우리가 프로젝트에서 작성한 Java 코드에 더불어서 각종 xml, propertise, jar 파일 등을 

JVM이나 WAS가 인식할 수 있도록 빌드해주는 도구들로 대표적인게 maven과 gradle이다.

 

Java code만 빌드할거라면 이클립스나 IntelliJ와 같은 IDE 빌드 도구만을 이용해도 되지만

프로젝트가 거대해질수록 xml, propertise, jar 등이 많이 추가되는데 이럴때 유용한 도구들이 바로

maven과 gradle이다.

 

Maven (Making the bulid process easy)

메이븐의 풀네임은 'Apache Maven'으로 2004년에 발표되었고  2023년을 기준으로

안정화된 Maven의 버전은 3.8.7로 2022년 12월 24일에 릴리즈되었다.

 

Maven은 근본적으로 Ant 단점을 해소하고자 출시된 것으로 의 장황한 빌드 스크립트를 개선했다.

( ※. Ant ? : Maven이전에 사용되던 자바기반의 빌드도구

      -> 프로젝트가 방대해지는 경우 스크립트 관리나 빌드 과정이 복잡해지는 단점이 존재함)

 

https://maven.apache.org/what-is-maven.html

 

Maven – Introduction

Introduction <!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you

maven.apache.org

maven의 official 공식 참고문서를 확인해보면 다음과 같이 정리되어있다.

 

1. 빌드를 쉽게

2. pom.xml을 이용한 정형화된 빌드 시스템

3. 양질의 프로젝트 정보 제공

4. 개발 가이드 라인 제공

5. 새로운 기능을 쉽게 설치하고 업데이트

 

위 5가지가 Maven을 사용하는 이유라고 볼 수있는데 기존에 사용되고 있던 Apache Ant 라는 빌드 관리 도구의

장황한 빌드 스크립트를 개선되어져 나온것이 특징이라고 볼 수 있다.

 

하지만 Maven역시 라이브러리가 서로 의존하는 경우 복잡해질 수 있다는 단점이 존재한다.

 

 

Gradle

Gradle은 Ant와 Maven의 장점을 모아서 2012년에 출시되었는데 Android OS의 빌드 도구로 채택되어 사용되어왔다.

Gradle이 출시되었을 때에는 Maven에서  dependency 엘리먼트 하위의 scope 엘리먼트를 통해 포함하려는 라이브러리의 범위를 지정할 수 있는 scope기능을 지원하지도 않고 성능적인 측면에서도 나을 것이 없었다.

 

Ant의 유연한 구조 + Maven의 편리한 의존성 관리 기능을 합쳐놓은 것만으로도 많은 인기를 얻었던 Gradle은

버전이 업데이트되면서 성능이라는 장점까지 더해지면서 대세가 되었다.

 

Maven을 통해서 익숙해진 XML 사용법을 버리고 Gradle을 사용하기 위해서 Grooby 문법을 배우는 것은

분명 Learning curve가 존재한다. 특히 협업을 하는 경우, 프로젝트 구성과 빌드만을 위해 모든 팀원이 Groovy 문법을

익혀야 한다는 사실은 Gradle을 사용하는데 큰 걸림돌이 된다.

 

# Gradle의 특징

1. 프로젝트를 설정 주입(Configuration Injection)방식으로 정의한다.

 -> 문자 그자체 의미로 필요한 정보가 있으면 그것을 프로젝트에 주입한다는 것을 의미한다.

 -> 프로젝트의 조건을 체크할 수 있게되어 프로젝트별로 주입되는 설정을 다르게 할 수 있다.

   : 이는 Groovy를 사용하기 때문에 동적으로 빌드를 할 수 있다는 것으로부터 오는 장점이라고 할 수 있다.

   Groovy 문법을 사용하기 때문에 XML을 사용하는 메이븐보다 가독성이 좋다는 것도 장점으로 볼 수 있다.

Gradle 설정파일의 가독성

2. 멀티 프로젝트 빌드가 가능하다.

 하나의  repositor내에 여러개의 하위 프로젝트를 구성할 수 있다.

하나의 모듈로 구현할 경우 겹치는 코드들을 전부 복붙해야하지만, gradle을 사용하면

위 이미지에서 보이는 Gradle 설정파일의 구조처럼 공통된 것따로 각 프로젝트에서 필요한 것따로 구성할 수 있다.

 

 

3. 빌드 속도가 빠르다.

 - 점진적 빌드 : 마지막 빌드 호출 이후에 task의 변경여부를 확인하여 task가 변경되지 않았다면 빌드를 수행하지 않는다.

이렇게 함으로써  변경되지 않은 부분에 있어서 불필요한 중복 빌드를 회피하기 때문에 빌드 속도가 빨라질 수 있다.

 

- 빌드 캐시(Build Cache)

 2개 이상의 빌드가 돌아가고, 하나의 빌드에서 사용되는 파일들이 다른 빌드들에 사용된다면 Gradle은 빌드 캐시에 올라온 이전 빌드의 결과물을 사용하기 때문에 다시 빌드하지 않기 때문에 빌드시간이 줄어들게 된다.

 

- 데몬 프로세스(Damon Process)

 데몬 프로세스란 서비스의 요청에 응답하기 위해 오래 동안 살아있는 프로세스를 의미하는데

Gradle의 데몬 프로세스는 메모리에다가 빌드 결과물을 보관하고 있기 때문에 한번 빌드된 프로젝트트 다음 빌드에서

매우 적은 시간만 소요된다.

 

Gradle VS Maven Performance를 참고하면 다양한 이유로 Maven보다 성능이 앞선다는 것을 설명하고 있다.

 

 

 

Groovy

JVM 상에서 실행되는 스크립트 언어로서 Java와 유사한 문법 구조를 가지며, 호환성이 아주 뛰어나  Java 클래스 파일을 그대로 Groovy 클래스로 사용할 수 있다.

 

 

Gradle 활용

 Gradle을 사용할 경우 필수적으로 알아야하는 build.gradle 파일의 dependencies 설정방법은 다음과 같다.

 

1. 의존 유형 ( Dependency types)

 

dependencies {
    // Local 라이브러리 모듈 or 프로젝트에 대한 종속성을 정의한다.
    implementation project(":mylibrary")

    // Local 바이너리 라이브러리에 대한 종속성을 정의한다.
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    // Remote 바이너리 라이브러리에 대한 종속성을 정의한다.
    implementation 'org.springframework.boot:spring-boot-starter'
}

위 예시처럼 Gradle은 3가지의 의존유형을 가지고 있다.

 

 # 1.1 Local 라이브러리 모듈 의존성 설정법

  : implementation project(":mylibrary")

 

프로젝트 구성시 Local에 정의된 모듈 혹은 프로젝트를 끌어와서 사용할 경우 settings.gradle 파일에 설정 된 "include:"로 정의한 프로젝트의 이름과 같아야한다.

 

# 1.2 Local 바이너리 라이브러리(jar) 의존성 설정

 Gradle은 build.gradle 파일을 기준으로 루트 경로가 정의되기 때문에 위의 예시라면

프로젝트path/libs/폴더내의 jar파일들 이러한 식으로 경로를 작성해야할 것이다.

 

# 1.3 Remote 바이너리 라이브러리 의존성 설정하기

 가장 많이 사용되는 의존성 선언 방식으로 외부에 정의되어 있는 바이너리 라이브러리 의존성을 설정한다.

아래 설정값에서 보이다시피  원격저장소인 repository에서 라이브러리들을 땡겨와서 의존성을 추가할 수 있다.

 

plugins {
    id 'org.springframework.boot' version '2.7.5'
    id 'io.spring.dependency-management' version '1.0.15.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

// repository 저장소 설정
repositories {
    mavenCentral()
    jcenter()
}

// 의존성 추가
dependencies {
    // implementation : 컴파일과 런타임 모두에 사용
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2'
    implementation 'org.mariadb.jdbc:mariadb-java-client:3.0.6'
    implementation 'org.bgee.log4jdbc-log4j2:log4jdbc-log4j2-jdbc4.1:1.16'

    compileOnly 'org.projectlombok:lombok'          // complie Only : 컴파일시에만 사용
    annotationProcessor 'org.projectlombok:lombok'

    testImplementation 'org.springframework.boot:spring-boot-starter-test' // test에서만 사용
}

 

 

2. 의존성 추가

API : 내부 의존성을 컴파일과 런타임 모두에 보이는 API 의존성

implementation : 내부 의존성을 런타임에서만 보이는 구현 의존성

compileOnly : 컴파일에만 사용되는 의존성 정의

runtimeOnly : 런타임에만 사용되는 의존성 정의

 

test + implementation or CompileOnly 등 : 해당 의존성을 테스트시에만 사용하도록 정의

 

내부의존성이란?

위 이미지에서 볼 수 있는 것처럼 B가 A를 라이브러리로 사용하고 있을때 (의존하고 있을 때) 

C가 A를 볼 수 있는 것을 내부의존성이라고한다.

 

implementation과 같은 경우에는 의존성을 최소화하였기에 빌드속도를 빠르게 할 수 있게한다.

api 같은 경우는 A가 수정되었을 시 C까지도 해당내용을 알고 있기 때문에 B뿐만아니라 C도 재빌드되어야한다.

반면 implementation 같은 경우는 내부 의존성이 컴파일 단계에서는 이루어지지 않으므로 B까지만 재빌드가 일어난다.