Spring

Gradle 설정 알아보기 (2편)

recent0 2025. 2. 23. 01:28

0. 서론


0.1 왜 이걸 찾아보게 되었는가?

  • 멀티 모듈 아키텍처에서 의존성을 정리하면서 다양한 개념들이 나왔지만 필요한 것만 골라서 찾아봄
  • 그 당시에는 어떤 역할을 하는지 간단하게 알아보고 사용했기에 잠시만 기억하고, 기억에 남아 있지 않음
  • 하지만 계속 사용할 거라고 생각했기 때문에 한번 정리가 필요하다고 생각

0.2 정리하고자 하는 내용

  • gradle의 역할 및 특징, build.gradle에서 볼 수 있는 설정들을 1편에서 다루었습니다 -> https://recently0.tistory.com/18
  • 의존성과 관련된 implementation, compileOnly, runtimeOnly, runtimeClasspath와 같은 설정들에 대해 알아보려고 합니다.

 

1. Java Library Plugin


1.1 Java Library Plugin

  • 초록색 설정은 사용자가 의존성을 선언하기 위해 사용
  • 핑크색 설정은 컴포넌트 컴파일이나 라이브러리 실행을 위해 사용
  • 파란색 설정은 컴포넌트 자체 내부에서 사용하기 위한 것

이렇게만 작성했을 때는 초록색 설정들은 사용하면서 접해왔기에 알 수 있지만, 핑크색 및 파란색 설정은 정확히 어떤 내용인지 확인하기 어려울 수 있습니다. 그래서 아래에 색깔별 그리고 비슷한 개념끼리 묶어서 정리했습니다.

 

2. compileClasspath, runtimeClasspath


2.1  compileClassPath, runtimeClassPath (with Test)

compileClasspath

  • 자바 컴파일러가 컴파일할때 필요한 classPath를 설정할 때 사용
  • testCompileClasspath도 동일하게 테스트에 필요한 classPath를 설정할 때 사용
  • 빌드 결과물에 포함되지 않음
  • api, implementation, compileOnly, compileOnlyApi 의존성이 이에 포함됨

runtimeClassPath

  • 런타임 때 필요한 classPath를 설정할 때 사용
  • testRuntimeClasspath도 동일하게 테스트에 필요한 classPath를 설정할 때 사용
  • 작성한 의존성은 빌드 결과물에 포함됨
  • api, implementation, runtimeOnly 의존성이 이에 포함됨

 

 

3. apiElements, runtimeElements


3.1 apiElements, runtimeElements

apiElements

  • apiElements는 해당 라이브러리를 컴파일하기 위해 필요한 모든 요소를 검색할 때 사용
  • 다른 모듈이나 프로젝트가 컴파일할 때 필요한 API 제공
  • api로 선언된 의존성만 포함하기 때문에 해당 모듈을 참조하는 다른 모듈은 api로 의존성 선언된 API만 사용할 수 있음

runtimeElements

  • runtimeElements는 해당 라이브러리를 실행하기 위해 필요한 모든 요소를 검색할때 사용
  • api, implementation, runtimeOnly로 선언된 모든 런타임 의존성을 포함

apiElements, runtimeElements 요소 확인

  • guava, apache coommons-lang, mysql-connector-java에 대한 의존성을 가지도록 구성
  • printConfigurations 테스크를 추가한 후 실행할 수 있도록 구성
  • 테스트 실행 apiElements는 guava 의존성만 출력, runtimeElements는 모든 의존성을 출력
plugins {
    id("java-library")
}

configurations {
    resolvedApiElements {
        canBeResolved = true
        extendsFrom apiElements
    }
    resolvedRuntimeElements {
        canBeResolved = true
        extendsFrom runtimeElements
    }
}

dependencies {
    api("com.google.guava:guava:31.0.1-jre")  // apiElements에 포함
    implementation("org.apache.commons:commons-lang3:3.12.0") // runtimeElements에만 포함
    runtimeOnly("mysql:mysql-connector-java") // runtimeElements에만 포함
}

// apiElements 및 runtimeElements 출력 테스크 추가
tasks.register("printConfigurations") {
	doLast {
		println "resolvedApiElements: " + configurations.resolvedApiElements.files
		println "resolvedRuntimeElements: " + configurations.resolvedRuntimeElements.files
	}
}

 

4. compileOnly, compileOnlyApi, runtimeOnly


4.1 compileOnly

어떤 역할을 하고 어떤 상황에 사용하는가?

  • 컴파일에는 필요하지만 런타임에 필요없을때 compileOnly를 사용

compileOnly 사용 예시

  • 예를 들어 Apache Commons Lang 라이브러리에 속한 StringUtils 클래스의 메서드를 아래와 같이 사용한다고 가정
  • 아래와 같이 compileClasspath를 기반으로 하는 의존성 설정 (ex.compileOnly, implementation)을 사용하면 문제가 없음
  • 만약 의존성을 설정하지 않는 경우에는 컴파일에 실패
public static void main(String[] args) {
    String text = "hello world";
    System.out.println(StringUtils.hasText(text));
}

dependencies {
    //compileClassPath를 기반으로 동작하는 의존성 설정
    compileOnly 'org.apache.commons:commons-lang3:3.12.0'
}

compileOnly, compileOnlyApi 비교

compileOnly, compileOnlyApi의 차이점은 다음과 같이 정리할 수 있습니다

  • 예를 들어 특정 라이브러리를 만드는데 Lombok을 사용해서 구현했다고 가정
  • 해당 라이브러리를 사용하는 유저는 Lombok을 사용하지는 않는다고 했을 때, 내부 모듈의 컴파일단계에서만 필요로 하므로 compileOnly를 사용
  • compileOnlyApi는 다른 모듈에도 전달하고 싶은 경우에 사용

차이 요약

  • compileOnly → 내부 모듈에서만 사용하고, 다른 모듈에서는 접근 못 함.
  • compileOnlyApi → 다른 프로젝트에서도 사용할 수 있지만, 실행할 때는 포함되지 않음.

4.2 runtimeOnly

runtimeOnly 역할

  • 사용하려는 라이브러리가 런타임에만 필요한 경우 사용
  • runtimeOnly를 사용해 컴파일 시간을 단축할 수 있음

runtimeOnly 예시

  • Spring Boot에서 JDBC 인터페이스를 기반으로 DB 연결을 설정했고, MySQL을 사용할 거라고 가정
  • 컴파일 타임에는 실제로 데이터베이스와의 연결이 중요치 않기에 MySQL JDDBC 드라이버가 필요 없음
  • 그렇기 때문에 아래와 같이 런타임에 필요한 mysql-connector를 runtimeOnly로 의존성 설정
dependencies {
    runtimeOnly 'mysql:mysql-connector-java:8.0.30' // MySQL JDBC 드라이버
}

 

5. api, implementation


5.1 api, implementation

api, implementation 공통점

  • 의존성 설정한 라이브러리가 compileClasspath, runtimeClasspath에 속하게 되어 사용

api, implementation 차이점

  • api는 모듈을 사용하는 소비자에게 의존성이 외부로 노출됨
  • implementation은 모듈을 사용하는 소비자에게 의존성이 노출되지 않아 해당 모듈 내부에서만 사용가능

api에 비해 implementation 장점

  • 외부로 의존성이 노출되지 않기 때문에 불필요한 의존성 전파를 막아서 컴파일 속도를 향상
  • 모듈 간에 불필요한 의존성들을 정리해 각 모듈을 조금 더 독립적으로 관리하기 용이해짐
  • 다만 언제나 implementation만 사용하는 것이 아니라 상황에 맞게끔 사용

api, implementation 사용 예시 

  • A, B 모듈이 있고, B 모듈이 A 모듈을 의존하고 있다고 가정
  • A 모듈에서 commons-lang 의존성을 implementation으로 사용했다고 가정
  • 이때 B 모듈은 A 모듈이 의존성 설정한 commons-lang 라이브러리를 사용할 수 없음.
  • implementation이 아닌 api로 선언했을 경우는 의존성이 전파되어 별도로 의존성을 설정하지 않더라도 사용 가능

A 모듈에서 라이브러리를 implementation으로 사용하고, B 모듈에서 의존하는 경우

dependencies {
     implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0'
}

 

6. 글을 마치며


여기까지 build.gradle의 dependencies 코드 블럭에서 볼 수 있는 의존성 설정들에 대해서 알아보았습니다. 자주 사용하던 개념들을 공부하다 보니 이를 바탕으로 gradle의 task, gradle의 장점으로 꼽히는 기능들의 원리, 그리고 이외에도 의존성만이 아닌 다양한 build.gradle의 설정들을 더 알아보면 좋겠다고 생각했습니다. 오늘도 읽어주셔서 감사합니다.