2026. 3. 24. 14:12ㆍgradle
[공식문서]: Creating a Multi Module Project
위 문서는,
하나의 루트 프로젝트 아래에
library모듈application모듈
를 두고,
application이 library를 Gradle 프로젝트 의존성으로 참조하도록 만드는 구조를 설명하고 있습니다.
즉, 단순한 단일 Spring Boot 프로젝트가 아니라,
재사용 가능한 라이브러리 모듈 + 실행 가능한 애플리케이션 모듈로 분리하는 구조를 설명하는 문서입니다.
1. Gradle 멀티 모듈의 가장 중요한 출발점: settings.gradle
이 문서에서 Gradle 기준으로 가장 먼저 등장하는 핵심은 이것입니다.
rootProject.name = 'gs-multi-module'
include 'library'
include 'application'
이 설정은 매우 중요합니다. 왜냐하면 Gradle에서 멀티 모듈 프로젝트의 실제 모듈 등록은 settings.gradle에서 이루어지기 때문입니다.
이 코드가 의미하는 것
rootProject.name = 'gs-multi-module'
루트 프로젝트의 이름을 지정합니다.
- 이 이름은 IDE에서 보이는 최상위 프로젝트 이름이 되고,
- Gradle이 전체 빌드 트리를 구성할 때 기준 이름이 됩니다.
include 'library'
루트 하위에 있는 library 디렉터리를 하나의 Gradle 서브프로젝트로 등록합니다.
include 'application'
루트 하위에 있는 application 디렉터리를 또 하나의 서브프로젝트로 등록합니다.
즉, 이 한 파일로 Gradle은 아래처럼 이해하게 됩니다.
- 루트 프로젝트:
gs-multi-module - 하위 모듈:
:library:application
여기서 : 표기법은 Gradle의 프로젝트 경로 표기입니다.
따라서 나중에 implementation project(':library') 같은 구문이 가능해집니다.
2. 루트 디렉터리의 의미
문서에서는 루트에 library, application 폴더를 두라고 설명합니다.
Gradle 관점에서 이 구조는 다음 의미를 가집니다.
root
├─ settings.gradle
├─ build.gradle (선택적)
├─ library
│ └─ build.gradle
└─ application
└─ build.gradle
문서에서는 루트에 텅빈 build.gradle을 선택적으로 둘 수 있다고 말합니다.
이건 “IDE가 루트 디렉터리를 프로젝트 루트로 더 잘 인식하도록 돕기 위한 용도”에 가깝습니다.
즉:
- 필수:
settings.gradle - 선택: 루트
build.gradle
라는 점이 중요합니다.
3. library 모듈의 Gradle 구성 분석
문서에는 library 모듈에 대해 두 단계가 나옵니다.
- Initializr가 만든 기본
build.gradle - 라이브러리 모듈에 맞게 조정한 최종
build.gradle
여기서 핵심은
library는 실행 애플리케이션이 아니라 “라이브러리 jar”이므로 Spring Boot executable jar를 만들면 안 된다는 점입니다.
3-1. library 모듈의 초기 Gradle 설정
초기 설정은 대략 이렇습니다.
plugins {
id 'org.springframework.boot' version '3.5.11'
id 'io.spring.dependency-management' version '1.1.7'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}
이 상태는 Spring Initializr가 기본적으로 “실행 가능한 Spring Boot 프로젝트”를 만들기 위해 생성한 형태입니다.
그런데 문제는 library는 실행 앱이 아니라 라이브러리라는 점입니다.
3-2. 왜 library에 org.springframework.boot 플러그인을 그대로 쓰면 안 되는가?
Spring Boot Gradle 플러그인은 대표적으로 다음을 해줍니다.
bootJar작업 활성화- 실행 가능한 fat jar / executable jar 생성
- Main(main 메서드가 정의된) 클래스 탐색
- 일반 Spring Boot 애플리케이션 실행 흐름에 필요한 여러 작업 자동 구성
그런데 library 모듈은 main() 메서드가 없습니다.
즉, 애플리케이션이 아니라 단지 다른 모듈이 가져다 쓰는 라이브러리 jar입니다.
그래서 이 플러그인을 그대로 적용하면 bootJar가 실행 가능한 jar를 만들려고 시도하고,
그 과정에서 메인 클래스가 없어서 문제가 생길 수 있습니다.
문서도 바로 그 이유 때문에 bootJar를 만들지 않도록 해야 한다고 설명합니다.
3-3. library 최종 Gradle 설정의 핵심
문서의 최종 library/build.gradle은 다음 구조입니다.
plugins {
id 'org.springframework.boot' version '3.5.11' apply false
id 'io.spring.dependency-management' version '1.1.7'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
repositories {
mavenCentral()
}
dependencyManagement {
imports {
mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES
}
}
dependencies {
implementation 'org.springframework.boot:spring-boot'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.withType(JavaCompile).configureEach {
options.compilerArgs.add("-parameters")
}
tasks.named('test') {
useJUnitPlatform()
}
이건 단순히 문법을 바꾼 게 아니라,
“Excutable 앱용 프로젝트”를 “라이브러리 프로젝트”로 성격 전환한 설정입니다.
4. apply false의 의미
이 문서에서 가장 중요한 Gradle 포인트 중 하나가 이것입니다.
id 'org.springframework.boot' version '3.5.11' apply false
왜 apply false를 쓰는가?
이 말은:
- 플러그인을 classpath에는 올려두되
- 현재 프로젝트에 실제로 적용하지는 않는다
는 뜻입니다.
즉:
- Spring Boot 플러그인의 클래스, 상수, BOM 좌표 등은 활용할 수 있지만
bootJar, 실행 jar 생성, 메인 클래스 처리 같은 실행 앱 중심 동작은 비활성화됩니다.
문서에서는 dependency management 기능은 유지하면서, executable jar 생성 동작은 막기 위한 목적으로 설명하고 있습니다.
이게 핵심입니다.
library는 Spring Boot 애플리케이션이 아니라
Spring Boot 생태계의 의존성 버전 관리 혜택만 받는 일반 Java 라이브러리로 다루는 것입니다.
5. 왜 spring-boot-starter가 아니라 spring-boot를 쓰는가?
문서에서 아주 중요한 문장이 하나 있습니다.
라이브러리는 가능한 한 더 좁은 의존성에 의존하는 것이 좋고,
그래서 spring-boot-starter 대신 org.springframework.boot:spring-boot를 사용하라고 합니다.
차이점
spring-boot-starter
- 여러 의존성을 한꺼번에 묶어둔 스타터
- 편하지만 필요 이상 의존성을 끌고 들어올 수 있음
spring-boot
- 더 핵심적인 기본 라이브러리 수준
- 라이브러리 모듈 입장에서는 불필요한 의존성 전파를 줄일 수 있음
즉, 문서의 의도는 이렇습니다.
library 모듈은 다른 프로젝트가 재사용할 대상이므로
의존성 전파를 최소화해야 한다.
이건 실무적으로도 매우 중요한 설계 원칙입니다.
6. dependencyManagement 블록의 의미
다음 부분도 매우 중요합니다.
dependencyManagement {
imports {
mavenBom org.springframework.boot.gradle.plugin.SpringBootPlugin.BOM_COORDINATES
}
}
이 구문은 Spring Boot Gradle 플러그인을 실제로 적용하지 않더라도,
Spring Boot가 관리하는 의존성 버전 체계(BOM) 를 가져오겠다는 뜻입니다.
왜 필요한가?
org.springframework.boot 플러그인을 적용하지 않으면,
Spring Boot 프로젝트에서 흔히 누리던 자동 버전 정렬 혜택이 사라질 수 있습니다.
예를 들어 아래처럼 버전을 명시하지 않아도:
implementation 'org.springframework.boot:spring-boot'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
적절한 Spring Boot 버전 체계에 맞게 정리되도록 하려면
BOM import가 필요합니다.
즉, 이 블록은 다음 역할을 합니다.
- Spring Boot 관련 라이브러리 버전을 중앙 관리
- 서로 호환되는 버전 조합 유지
- 라이브러리 모듈도 Spring Boot 버전 생태계에 맞춤
7. -parameters 옵션을 왜 직접 추가하는가?
문서에서 이것도 굉장히 중요한 포인트입니다.
tasks.withType(JavaCompile).configureEach {
options.compilerArgs.add("-parameters")
}
문서 설명에 따르면, Spring Boot 플러그인을 비활성화하면
이전에는 자동으로 설정되던 JavaCompiler의 -parameters 옵션도 더 이상 자동 적용되지 않습니다.
-parameters가 뭐냐?
이 옵션은 Java 컴파일 시 메서드 파라미터 이름을 바이트코드에 보존하게 합니다.
예를 들어:
public String hello(String message)
컴파일 후에도 message라는 파라미터 이름을 런타임에서 참조할 수 있게 해줍니다.
Spring 계열에서는 다음 같은 경우 중요합니다.
@ConfigurationProperties- SpEL
- 리플렉션 기반 파라미터 이름 참조
- 생성자 바인딩
- 일부 테스트/프레임워크 기능
즉, 문서는 단순히 “추가하세요”가 아니라
Spring Boot 플러그인 비활성화의 부작용까지 보정하고 있는 것입니다.
이 부분이 상당히 실무적입니다.
8. testRuntimeOnly 'org.junit.platform:junit-platform-launcher'의 의미
최종 library/build.gradle에는 이것도 추가되어 있습니다.
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
이건 테스트 실행 시 JUnit Platform 런처를 런타임에 제공하기 위한 설정입니다.
보통 최신 Gradle/Spring Boot 환경에서는 테스트 실행과 IDE 연동, 플랫폼 런처 쪽에서 안정성을 위해 같이 넣는 경우가 있습니다.
핵심은:
- 테스트 컴파일용이 아니라
- 테스트 실행 시점(runtime) 에 필요한 의존성
이라는 점입니다.
9. application 모듈의 Gradle 설정 분석
이제 실행 가능한 앱인 application 모듈입니다.
문서의 최종 Gradle 설정은 대략 다음입니다.
plugins {
id 'org.springframework.boot' version '3.5.11'
id 'io.spring.dependency-management' version '1.1.7'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation project(':library')
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}
이 설정은 library와 다르게 실행 애플리케이션 성격에 맞춰져 있습니다.
9-1. 왜 application에는 Spring Boot 플러그인을 정상 적용하는가?
application은 실제로 실행할 프로그램이므로
id 'org.springframework.boot' version '3.5.11'
를 그대로 적용합니다.
즉 이 모듈은:
bootJar생성 가능bootRun가능- 메인 클래스 탐색
- 실행형 Spring Boot 애플리케이션 빌드
를 수행해야 합니다.
9-2. implementation project(':library')의 의미
이 문서의 핵심 의존성 연결은 바로 이것입니다.
implementation project(':library')
이 한 줄이 의미하는 것은:
application 모듈이 외부 jar가 아니라
같은 Gradle 멀티 프로젝트 안의 :library 모듈에 직접 의존한다는 것입니다.
이게 왜 중요한가?
Gradle은 이 구문을 보면 다음을 이해합니다.
application을 빌드하기 전에library를 먼저 빌드해야 함library의 컴파일 아티팩트를application의 클래스패스에 넣어야 함- 모듈 간 변경 사항을 연쇄적으로 반영해야 함
즉, 단순 문자열 의존성이 아니라 프로젝트 그래프(project graph) 를 구성합니다.
10. 멀티 모듈 빌드 흐름의 실제 의미
문서의 실행 예시는 다음입니다.
./gradlew build && ./gradlew :application:bootRun
이 명령은 사실상 두 단계입니다.
1단계: ./gradlew build
전체 멀티 프로젝트를 빌드합니다.
즉:
- 루트 프로젝트 기준으로
library빌드application빌드- 각 모듈의 테스트 수행
같은 흐름이 일어납니다.
2단계: ./gradlew :application:bootRun
application 모듈만 지정해서 실행합니다.
여기서 :application:bootRun은 Gradle의 프로젝트 경로 방식입니다.
:= 루트 기준application= application 모듈bootRun= 해당 모듈의 Spring Boot 실행 태스크
즉, 멀티 모듈에서 특정 모듈만 실행하려면
이런 식의 프로젝트 경로 기반 태스크 호출을 사용한다는 점을 보여줍니다.
11. wrapper 이동 부분의 의미
문서에서는 library를 Initializr로 만들면 거기에 gradlew, gradle/ 등이 들어있을 수 있고,
그걸 루트로 옮기라고 설명합니다.
이건 Gradle 멀티 모듈에서 매우 자연스러운 구조입니다.
왜 루트에 wrapper를 둬야 하나요?
멀티 모듈 빌드는 보통 루트에서 통합 제어해야 하기 때문입니다.
즉, 루트에 다음이 있어야 편합니다.
gradlewgradlew.batgradle/wrapper/...
그래야 루트에서 아래처럼 전체 제어가 가능합니다.
./gradlew build
./gradlew :application:bootRun
./gradlew :library:test
즉, wrapper는 서브모듈 단위가 아니라
멀티 프로젝트 전체를 대표하는 루트에 두는 것이 일반적입니다.
12. 문서가 암묵적으로 전제하는 Gradle 개념들
이 문서는 설명을 길게 하진 않지만, 사실 아래 개념들을 전제로 하고 있습니다.
12-1. Gradle은 “프로젝트 간 의존성”을 이해한다
implementation project(':library')는 단순 jar 경로 참조가 아닙니다.
Gradle은
- 어떤 프로젝트가 누구에게 의존하는지
- 어떤 태스크를 먼저 수행해야 하는지
- 어떤 산출물을 연결해야 하는지
를 그래프로 관리합니다.
12-2. plugins 블록은 프로젝트 성격을 결정한다
같은 Java 프로젝트라도
org.springframework.boot적용 여부apply false여부
에 따라
- 실행 앱
- 일반 라이브러리
- 의존성 관리 전용 모듈
등으로 성격이 달라집니다.
12-3. 라이브러리 모듈과 애플리케이션 모듈은 Gradle 설정이 달라야 한다
문서의 가장 중요한 메시지 중 하나입니다.
library
- 실행 jar 불필요
- 최소 의존성 선호
- Spring Boot plugin 직접 적용하지 않음
- BOM만 가져옴
application
- 실행 jar 필요
bootRun필요- Spring Boot plugin 직접 적용
이 둘을 같은 방식으로 만들면 안 된다는 점을 문서가 분명히 보여줍니다.
13. 이 문서의 Gradle 설계 의도를 한 문장으로 정리하면
이 문서는 Gradle에서 다음 구조를 만들려는 것입니다.
루트 settings.gradle로 멀티 모듈을 선언하고,
라이브러리 모듈은 일반 Java/Spring 라이브러리로,
애플리케이션 모듈은 실행 가능한 Spring Boot 앱으로 분리 구성한다.
14. 실무 관점에서 매우 중요한 포인트 정리
1) settings.gradle이 멀티 모듈의 시작점입니다
include 'library', include 'application' 없이는 멀티 모듈이 아닙니다.
2) library에 Spring Boot 플러그인을 그대로 적용하면 안 됩니다
실행 앱이 아닌데 bootJar가 활성화되기 때문입니다.
3) 그래서 apply false + BOM import 조합을 씁니다
플러그인 기능 전체가 아니라 의존성 버전 관리 혜택만 취하는 전략입니다.
4) 라이브러리는 가능한 한 좁은 의존성을 가져야 합니다
spring-boot-starter보다 spring-boot를 택한 이유가 바로 이것입니다.
5) application은 implementation project(':library')로 내부 모듈 의존성을 겁니다
이게 멀티 모듈의 핵심 연결입니다.
6) wrapper는 루트에 두는 것이 맞습니다
루트에서 전체 프로젝트 빌드를 통제하기 위해서입니다.
15. 이 문서를 바탕으로 이해해야 할 Gradle 최종 그림
아래처럼 이해하시면 가장 정확합니다.
루트
settings.gradle- 전체 서브프로젝트 등록
gradlew- 전체 빌드 진입점
library
- 실행용 앱 아님
- 일반 jar
- Spring Boot dependency ecosystem만 활용
bootJar성격 제거
application
- 실제 실행 앱
library를 내부 의존성으로 사용bootRun,bootJar가능
16. 이 문서의 핵심 문장
Gradle만 기준으로 이 문서의 본질은 딱 이겁니다.
멀티 모듈에서는 모든 모듈이 같은 성격이 아니므로,
Spring Boot 애플리케이션 모듈과 재사용 라이브러리 모듈의 Gradle 설정을 구분해야 한다.
'gradle' 카테고리의 다른 글
| Creating a Multi Module Project[3] (0) | 2026.03.25 |
|---|---|
| Root 디렉토리의 build.gradle (0) | 2026.03.24 |
| Creating a Multi Module Project[2] (0) | 2026.03.24 |
| sync build.gradle (0) | 2026.03.23 |
| build.gradle (0) | 2026.03.23 |