자바를 개발을 위한 gradle 총 정리

img.png

gradle 이란?

Gradle은 오픈소스 빌드 자동화 시스템으로, Groovy 또는 Kotlin DSL로 만들어진 스크립트를 활용해서 빌드를 자동화합니다.

비슷한 빌드 자동화 도구로 maven, ant 등이 존재합니다.

gradle 구조

Gradle은 특정한 프로젝트 구조를 가정합니다. 커스텀하게 구조를 설정할 수는 있지만 웬만해서는 커스텀하지않습니다.

  • src/main/kotlin: 프로젝트의 코틀린 소스 파일이 위치하는 곳입니다.
  • src/main/resources: 코틀린 코드에서 사용될 수 있는 리소스 파일을 저장하는 곳입니다.
  • src/test/kotlin: 테스트를 위한 코틀린 소스 파일이 위치하는 곳입니다.
  • src/test/resources: 테스트에 사용되는 리소스 파일을 저장하는 곳입니다.

build.gradle 구조

dependencies

dependencies 블록은 프로젝트가 필요로 하는 외부 라이브러리를 선언하는 곳입니다.

이를 통해 Gradle은 개발자가 지정한 레포지토리에서 필요한 jar 파일을 다운로드하고 프로젝트 빌드 시 이를 포함시킵니다.

plugins

Gradle plugin은 Gradle 빌드 프로세스에 task를 추가하기 위한 코드 묶음입니다.

plugin은 빌드 스크립트에 재사용 가능한 빌드 로직을 제공하고, 특정 task를 수행하거나 빌드 프로세스를 구성하도록 돕습니다.

예를 들어서 아래와 같습니다.

  • Java 플러그인: Java 소스 파일을 컴파일하고, jar 파일을 생성하고, 테스트를 실행하는 기본적인 태스크를 제공합니다.
  • Spring Boot 플러그인: Spring Boot 애플리케이션을 위한 실행 가능한 bootJar 파일을 생성하고, Spring Boot 특화 설정을 자동화합니다.

참고로 plugins들은 Gradle Plugin Portal에서 가져옵니다.

subprojects, allprojects

subprojects는 프로젝트를 제외한 모든 모듈에게 공통적으로 적용되는 블록입니다.

allprojects는 프로젝트를 포함한 모든 모듈에게 공통적으로 적용되는 블록입니다.

예시

plugins {
    // 'java' 플러그인을 적용합니다. 이것은 자바 컴파일과 관련된 기본적인 태스크들을 제공합니다.
    id 'java'
}

repositories {
    // 의존성을 다운로드 받을 중앙 저장소를 지정합니다.
    mavenCentral()
}

subprojects{
    dependencies {
        // 프로젝트의 의존성을 정의합니다.
        testImplementation 'junit:junit:4.12'
    }
}
// 여기에 추가적인 태스크를 정의할 수 있습니다.

gradle build 실행 순서

Gradle이 gradle build 명령을 실행할 때, 아래 과정을 따릅니다.

  1. 스크립트 파싱과 컴파일: 스크립트 파싱과 컴파일: Gradle 실행 엔진은 build.gradle 또는 build.gradle.kts 파일 내의 스크립트를 먼저 파싱합니다. Gradle 실행 엔진은 이를 읽고 해석하여 바이트 코드로 컴파일합니다. 이 과정은 프로젝트를 구성하고 필요한 빌드 태스크를 정의하는 데 사용됩니다.
  2. 의존성 해석 및 캐싱: 이 컴파일된 스크립트를 통해, Gradle은 정의된 레포지토리(예: Maven Central, JCenter)에서 필요한 의존성을 찾아 해석하고 다운로드합니다. Gradle 데몬이 활성화된 상태라면, 이러한 의존성 해석 결과와 다운로드된 라이브러리 파일, 그리고 이전에 실행된 태스크의 결과를 캐시하여, 동일한 작업을 재실행할 때 빌드 시간을 단축시킵니다.
  3. 소스 코드 컴파일: 모든 의존성이 준비되면, Gradle은 프로젝트의 src/main/kotlin에 위치한 개발자의 소스 코드를 컴파일합니다. 컴파일된 바이트 코드는 .class 파일로 변환되어 빌드 디렉토리에 저장됩니다. 이 .class 파일들은 최종적으로 JAR 또는 WAR 파일과 같은 실행 가능한 아티팩트로 패키징되어 배포에 사용됩니다.

gradlew

gradlew는 gradle을 매번 설치할 필요 없이, 동일한 환경에서 실행할 수 있도록 해줍니다.

gradle 설치 없이 gradlew 명령어를 사용하면, gradlew-wrapper.properties에 명시된 버전의 gradle을 실행시켜줍니다.

img.png

참고로 인텔리제이에서 gradle을 refresh하면 gradlew를 통해서 동작합니다.

multi module project

gradle이 설치되어있는 상태에서, gradle init 명령어를 실행하면 아래 이미지처럼 멀티모듈 프로젝트 셋팅이 가능합니다.

  1. 먼저 어떤 프로젝트를 생성할 것인지 선택합니다.
  2. 어떤 언어를 사용할 것인지 선택합니다.
  3. 멀티모듈 프로젝트를 생성할것인지 선택합니다.
  4. build.gradle 스크립트를 어떤 언어로 만들것인지 선택합니다.
  5. 프로젝트 이름을 선택합니다.
  6. 자바 버전을 선택합니다.
  7. 실험적인 최신 기능을 사용할것인지 선택합니다. img.png

project 디렉토리에보면 settings.gradle 파일이 보입니다.

확인해보면 여러가지 모듈들을 include 하고있는것을 볼 수 있습니다. img.png

gradle build 명령어를 사용하면 프로젝트의 하위모듈들이 같이 빌드되는것을 볼 수 있습니다.

img.png

원한다면 subproject path를 아래처럼 수정할 수 있습니다.

img.png
(공식문서 링크)

또한 루트 프로젝트에 build.gradle.kts를 추가적으로 만들어서, allprojects, subprojects 블록으로 전체 프로젝트의 일관된 설정을 관리할 수 있습니다.

dependency configuration

api : A모듈이 B모듈을 api로 의존할 때, 그리고 B모듈이 C모듈에 의존하고 있을 때, A모듈은 C모듈에 컴파일 타임에 접근할 수 있습니다. 즉, A모듈이 C모듈의 API를 직접 사용할 수 있습니다. 예를 들어, B모듈이 제공하는 함수가 C모듈의 객체를 반환할 경우, A모듈은 그 객체를 직접 다룰 수 있습니다.

implementation : A모듈이 B모듈을 implementation으로 의존하면, B모듈이 C모듈에 의존하고 있어도 A모듈은 C모듈에 컴파일 타임에 접근할 수 없습니다. 이는 A모듈이 B모듈의 내부 구현에는 관심이 없고, 오직 B모듈이 제공하는 API만을 사용하겠다는 것을 의미합니다. 이 방식은 layered 아키텍처을 따르며 모듈 간의 결합도를 낮추는데 유리합니다. 내부 구현의 변경이 외부 모듈에 영향을 미치지 않도록 합니다.

compileOnly : 컴파일 시에만 필요하고 런타임에는 필요 없을 때 사용됩니다. 이 의존성은 jar로 패키징될 때 포함되지 않습니다. 예를 들어, 롬복은 자바 컴파일러의 플러그인을 이용해 컴파일 시에 바이트 코드를 조작하기 때문에, 컴파일된 이후에는 필요 없습니다. 마찬가지로, slf4j-api는 로깅을 위한 인터페이스를 제공하지만, 실제 로깅 구현체(ex logback)는 런타임에 필요합니다. 스프링 부트 스타터는 기본적으로 slf4j와 logback을 제공합니다.

runtimeOnly : 런타임 시에 필요하지만, 컴파일 시에는 필요하지 않을 때 사용됩니다. 이 의존성은 런타임 환경에만 추가되며, 컴파일 시에는 고려되지 않습니다. 예를 들어, JDBC 드라이버는 런타임에 리플랙션을 통해 참조되지만, 컴파일 타임에는 직접 참조되지 않습니다. 이러한 방식은 런타임에만 특정 구현체를 요구하는 경우에 유용합니다.

참고로 api와 implementation는 컴파일시에만 차이점이 존재하고, 런타임에는 추가한 의존성에 둘 다 접근이 가능합니다.

모듈간의 Dependencies

위에서 만들어진 app모듈의 build.gradle.kts에, 아래처럼 springboot 플러그인을 추가합니다.

plugins {
    id("buildlogic.kotlin-application-conventions")
    id("org.springframework.boot") version "2.7.17"
}

dependencies {
    implementation("org.apache.commons:commons-text")
    implementation(project(":utilities"))
}

그리고 gradle build 명령어를 실행하면 build/libs 위치에 bootJar가 만들어집니다.

그러고 jar xf app.jar 를 입력하면 app.jar을 풀어볼 수 있고, utilities.jar를 확인할 수 있습니다. img.png

gradle app:dependencies 를 통해서도 의존성을 확힌할 수 있습니다.

img.png

인텔리제이에서도 손쉽게 확인할 수 있습니다.

img.png

모듈간의 사용하는 라이브러리 버전이 꼬이는 경우가 종종 발생합니다.

이 경우 컴파일시에는 메소드가 존재하지만 런타임에 로딩하는 클래스 버전에는 해당 메소드가 존재하지않으면서, NoSuchMethodException이 발생할 수 있습니다.

이 때 위 기능들을 이용하면 디버깅하는데 큰 도움이 됩니다.

효과적인 성능 튜닝 방법

static version을 사용

의존성을 주입할 때, dynamic version 보다는 static version을 사용하면 성능을 향상시킬 수 있습니다.

dynamic version을 사용하면 최신 버전이 있는지 체크하기 때문에 추가 I/O가 발생하기 때문입니다.

불필요한 의존성을 제거

불필요한 의존성은 제거함으로써 의미없는 의존성을 다운받는 시간을 줄일 수 있습니다.

gradle version up

Gradle 개발자들이 지속적으로 성능 향상을 위해 튜닝해주기 때문에, 버전이 높을수록 더 퍼포먼스가 좋습니다.

병렬 처리 옵션

org.gradle.parallel=true 를 추가해줍니다. gradle 4.0 이상으로는 기본적으로 켜져있습니다.

아래와 같은 구조에서 applicatiom->domain, infra->domain 의존성 처리를 병렬로 진행합니다.

api -> application -> domain
    -> infra       -> domain

gradle daemon 옵션

org.gradle.daemon=true 를 추가해줍니다. gradle 3.0 이상으로는 기본적으로 켜져있습니다.

  • JVM을 백그라운드에서 실행시켜, 매번 새로운 Gradle 빌드가 JVM을 시작할 필요가 없도록 함으로써 빌드 시간을 줄입니다.
  • 이미 빌드된 프로젝트 정보를 메모리에 캐싱하여 다음 빌드 때 재사용합니다.
  • 파일 시스템의 변경 사항을 감지하여 어떤 부분을 재빌드할 필요가 있는지 계산합니다.
  • 런타임 최적화를 이용하여 빌드 성능을 지속적으로 향상시킵니다.

gradle configuration cache 옵션

org.gradle.configuration-cache=true 를 추가해줍니다.

아래 항목들을 캐싱합니다.

  • 초기화, 설정, 빌드 스크립트
  • 빌드 구성 단계에서 사용되는 시스템 속성
  • 빌드 구성 단계에서 사용되는 Gradle 속성
  • 빌드 구성 단계에서 사용되는 환경 변수
  • providers와 같은 값 공급자를 사용하여 접근하는 구성 파일
  • buildSrc 입력, 포함된 빌드 구성 입력 및 소스 파일

kapt

kapt 명령어는 애노테이션 프로세서를 활성화 시킵니다.

애노테이션 프로세서는 어노테이션의 메타데이터를 기반으로 추가적인 소스 코드를 생성하거나, 기존 코드를 검증하는 등의 작업을 자동화합니다.

아래 순서로 동작합니다.

애노테이션 검색: 컴파일러는 소스 코드 내의 모든 애노테이션을 스캔합니다. 프로세서 실행: 해당 애노테이션을 처리할 수 있는 애노테이션 프로세서를 찾아 실행합니다. 코드 생성 및 수정: 애노테이션 프로세서는 필요한 경우 추가적인 소스 코드를 생성하거나, 기존 코드를 수정합니다. 컴파일 계속 진행: 생성된 코드는 프로젝트의 나머지 부분과 함께 컴파일됩니다.

예시로 어노테이션 프로세서는 컴파일 시점에 @Entity 어노테이션을 기반으로 QueryDSL Q타입 클래스를 자동으로 생성할 수 있습니다.

참조 래퍼런스

  • https://docs.gradle.org/current/userguide/performance.html#enable_daemon
  • https://www.udemy.com/course/gradle-for-java-developers/
  • https://sabarada.tistory.com/198

© 2021. All rights reserved.

Powered by Hydejack v9.2.1