- 실행가능한 압축파일 패키징(Packaging Executable Archives)
- 4.1. 실행 가능한 Jar 패키징(Packaging Executable Jars)
- 4.2. 실행 가능한 War 패키징(Packaging Executable Wars)
- 4.2.1. 실행 가능하고 배포 가능한 war 패키징(Packaging Executable and Deployable Wars)
- 4.3. 실행 가능한 압축파일과 plain 압축파일 패키징(Packaging Executable and Plain Archives)
- 4.4. 실행가능한 압축파일 패키징 구성(Configuring Executable Archive Packaging)
- 4.4.1. 메인 클래스 구성(Configuring the Main Class)
- 4.4.2. 개발 전용 의존성 포함(Including Development-only Dependencies)
- 4.4.3. 압축 풀기위한 라이브러리 구성(Configuring Libraries that Require Unpacking)
- 4.4.4. 압축파일을 완전히 실행 가능하게 만들기(Making an Archive Fully Executable)
- 4.4.5. PropertiesLauncher 사용(Using the PropertiesLauncher)
- 4.4.6. 계층화된 Jar 또는 war 패키징(Packaging Layered Jar or War)
- 계층 구성 커스텀(Custom Layers Configuration)
- 실행가능한 압축파일 패키징(Packaging Executable Archives)
Chapter 4. 실행가능한 압축파일 패키징(Packaging Executable Archives)
플러그인은 애플리케이션의 모든 의존성을 포함하고 java -jar
로 실행할 수 있는 실행 가능한 압축파일(jar 파일 및 war 파일)를 생성할 수 있다.
4.1. 실행 가능한 Jar 패키징(Packaging Executable Jars)
실행 가능한 jar는 bootJar
태스크(Task)를 사용하여 빌드할 수 있다. 태스크는 java
플러그인이 적용될 때 자동 생성되며 BootJar
의 인스턴스이다. assemble
태스크는 bootJar
태스크에 따라 자동 구성되므로 assemble
(또는 build
)을 실행하면 bootJar
작업도 실행된다.
4.2. 실행 가능한 War 패키징(Packaging Executable Wars)
실행 가능한 war는 bootWar
태스크를 사용하여 구축할 수 있다. 태스크는 war
플러그인 적용 시 자동으로 생성되며 BootWar
의 인스턴스이다. assemble
태스크는 bootWar
태스크에 따라 자동으로 구성되므로 assemble
(또는 build
)을 실행하면 bootWar
태스크도 실행된다.
4.2.1. 실행 가능하고 배포 가능한 war 패키징(Packaging Executable and Deployable Wars)
war 파일은 java -jar
를 사용하여 실행하고 외부 컨테이너에 배포하도록 패키징할 수 있다. 그렇게 하려면, 내장된 서블릿 컨테이너 의존성을 providedRuntime
구성에 추가해야 한다. 예제는 다음과 같다.
Groovy
dependencies {
implementation('org.springframework.boot:spring-boot-starter-web')
providedRuntime('org.springframework.boot:spring-boot-starter-tomcat')
}
Kotlin
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
providedRuntime("org.springframework.boot:spring-boot-starter-tomcat")
}
이렇게 하면 외부 컨테이너의 자체 클래스와 충돌하지 않는 war 파일의 WEB-INF/lib-provided
디렉토리에 패키징된다.
그레이들은
compileOnly
구성보다providedRuntime
이 선호되며, 무엇보다compileOnly
의존성은 테스트 클래스패스에 없기 때문에 모든 웹 기반 통합 테스트가 실패한다.
4.3. 실행 가능한 압축파일과 plain 압축파일 패키징(Packaging Executable and Plain Archives)
기본적으로, bootJar
또는 bootWar
태스크가 구성됐을 때, jar
또는 war
태스크는 압축파일 분류자(archive Classifier) 규약으로 plain
이 추가로 구성된다. 이렇게 하면 bootJar
와 jar
또는 bootWar
와 war
가 서로 다른 출력 위치를 갖게 되어 실행 가능한 압축파일과 일반 압축파일을 동시에 빌드할 수 있다.
만약 일반 압축파일이 아닌 실행 가능한 압축파일에 분류자를 사용하는 것을 선호하는 경우, jar
및 bootJar
태스크에 대해 다음 예제에 표시된 대로 분류자를 구성한다:
Groovy
tasks.named("bootJar") {
archiveClassifier = 'boot'
}
tasks.named("jar") {
archiveClassifier = ''
}
Kotlin
tasks.named<BootJar>("bootJar") {
archiveClassifier.set("boot")
}
tasks.named<Jar>("jar") {
archiveClassifier.set("")
}
또는, 일반 압축파일이 빌드 되지 않게 하려면, jar
태스크에 대한 다음 예제와 같이 해당 태스크를 비활성화해야 한다.
Groovy
tasks.named("jar") {
enabled = false
}
Kotlin
tasks.named<Jar>("jar") {
enabled = false
}
4.4. 실행가능한 압축파일 패키징 구성(Configuring Executable Archive Packaging)
BootJar
및 BootWar
태스크는 각각 그레이들의 Jar
및 War
태스크의 하위 클래스이다. 결과적으로, 모든 표준 구성 옵션은 jar 또는 war를 패키징할 때 사용할 수 있고, 실행 가능한 jar 또는 war를 패키징할 때도 사용할 수 있다. 실행 가능한 jar 및 war에 특정한 여러 구성 옵션도 제공된다.
4.4.1. 메인 클래스 구성(Configuring the Main Class)
기본적으로, 실행 가능한 압축파일의 메인(main) 클래스는 태스크의 클래스 패스(class path) 내의 디렉토리에서 public static void main(String[])
메서드가 있는 클래스를 찾아 자동으로 구성된다.
메인(main) 클래스는 태스크의 mainClass
프로퍼티를 사용하여 명시적으로 구성할 수도 있다:
Groovy
tasks.named("bootJar") {
mainClass = 'com.example.ExampleApplication'
}
Kotlin
tasks.named<BootJar>("bootJar") {
mainClass.set("com.example.ExampleApplication")
}
또는, 메인(main) 클래스 이름은 스프링 부터 DSL의 mainClass
프로퍼티를 사용하여 프로젝트 전체에 구성할 수 있다:
Groovy
springBoot {
mainClass = 'com.example.ExampleApplication'
}
Kotlin
springBoot {
mainClass.set("com.example.ExampleApplication")
}
만약 application
플러그인이 적용된 경우 해당 mainClass
프로퍼티를 반드시 구성해야 하며 동일한 용도로 사용할 수 있다:
Groovy
application {
mainClass = 'com.example.ExampleApplication'
}
Kotlin
application {
mainClass.set("com.example.ExampleApplication")
}
마지막으로, 태스크의 매니페스트에서 Start-Class
attribute을 구성할 수 있다.
Groovy
tasks.named("bootJar") {
manifest {
attributes 'Start-Class': 'com.example.ExampleApplication'
}
}
Kotlin
tasks.named<BootJar>("bootJar") {
manifest {
attributes("Start-Class" to "com.example.ExampleApplication")
}
}
만약, 메인 클래스가 코틀린으로 작성된 경우, 생성된 자바 클래스의 이름을 사용해야 한다. 기본적으로,
Kt
접미사가 추가된 코트린 클래스의 이름이다. 예를 들어,ExampleApplication
은ExampleApplicationKt
가 된다.@JvmName
을 사용하여 다른 이름이 정의된 경우 해당 이름을 사용해야 한다.
4.4.2. 개발 전용 의존성 포함(Including Development-only Dependencies)
기본적으로 developmentOnly
구성에 선언된 모든 의존성은 실행 가능한 jar 또는 war에서 제외된다.
압축파일의 developmentOnly
구성에 선언된 의존성을 포함하려면, bootWar
태스크에 대한 다음 예제처럼, 구성을 포함하도록 태스크의 클래스패스를 구성한다:
Groovy
tasks.named("bootWar") {
classpath configurations.developmentOnly
}
Kotlin
tasks.named<BootWar>("bootWar") {
classpath(configurations["developmentOnly"])
}
4.4.3. 압축 풀기위한 라이브러리 구성(Configuring Libraries that Require Unpacking)
대부분의 라이브러리는 실행 가능한 압축파일에 중첩될 때 직접 사용할 수 있지만, 특정 라이브러리에는 문제가 있을 수 있다. 예를 들어 JRuby에는 jruby-complete.jar
을 항상 파일 시스템에서 직접 사용 가능하다고 가정하는 자체 중첩된 jar 지원이 포함되어 있다.
문제가 있는 라이브러리를 처리하기 위해, 실행 가능한 압축파일이 실행될 때 특정 중첩된 jar의 압축을 임시 디렉토리에 풀도록 실행 가능한 압축파일을 구성할 수 있다. 라이브러리는 소스 jar 파일의 절대 경로와 일치하는 앤트-스타일(Ant-style)패턴을 사용하여 압축 해제가 필요한 것으로 식별할 수 있다:
Groovy
tasks.named("bootJar") {
requiresUnpack '**/jruby-complete-*.jar'
}
Kotlin
tasks.named<BootJar>("bootJar") {
requiresUnpack("**/jruby-complete-*.jar")
}
더 많은 제어를 위해 클로저(closure)를 사용할 수도 있다. 클로저는 FileTreeElement
로 전달되며 압축 해제가 필요한지 여부를 나타내는 boolean
을 반환해야 한다.
4.4.4. 압축파일을 완전히 실행 가능하게 만들기(Making an Archive Fully Executable)
Spring Boot는 완전히 실행 가능한 압축파일을 지원한다. 애플리케이션 실행 방법을 알고 있는 셸 스크립트를 추가하여 압축파일을 실행 가능하게 만든다. 유닉스와 같은 플랫폼에서, 시작 스크립트를 사용하여 압축파일을 다른 실행 파일처럼 직접 실행하거나 서비스로 설치할 수 있다.
현재 일부 도구는 이 형식을 허용하지 않으므로 항상 이 기술을 사용하지 못할 수도 있다. 예를 들어
jar -xf
는 완전히 실행 가능한 jar 또는 war를 자동으로 추출하지 못할 수 있다.java -jar
로 실행하거나 서블릿 컨테이너(servlet container)에 배포하는 것 보단, 직접 실행하려는 경우 이 옵션을 활성화하는 것이 좋다.
이 기능을 사용하려면, 실행 스크립트 포함을 활성화해야 한다:
Groovy
tasks.named("bootJar") {
launchScript()
}
Kotlin
tasks.named<BootJar>("bootJar") {
launchScript()
}
이렇게 하면 스프링 부트의 기본 실행 스크립트가 압축파일에 추가된다. 기본 시작 스크립트에는 적절한 기본값이 있는 여러 속성이 포함되어 있다.properties
프로퍼티를 사용하여 값을 사용자 지정할 수 있다:
Groovy
tasks.named("bootJar") {
launchScript {
properties 'logFilename': 'example-app.log'
}
}
Kotlin
tasks.named<BootJar>("bootJar") {
launchScript {
properties(mapOf("logFilename" to "example-app.log"))
}
}
기본 시작 스크립트가 요구 사항을 충족하지 않는 경우, 스크립트 프로퍼티를 사용하여 사용자 지정 시작 스크립트를 제공할 수 있다:
Groovy
tasks.named("bootJar") {
launchScript {
script = file('src/custom.script')
}
}
Kotlin
tasks.named<BootJar>("bootJar") {
launchScript {
script = file("src/custom.script")
}
}
4.4.5. PropertiesLauncher 사용(Using the PropertiesLauncher)
PropertiesLauncher
를 사용하여 실행 가능한 jar 또는 war를 실행하려면, 작업의 매니페스트를 구성하여 Main-Class
애트리뷰트를 설정하자.
Groovy
tasks.named("bootWar") {
manifest {
attributes 'Main-Class': 'org.springframework.boot.loader.PropertiesLauncher'
}
}
Kotlin
tasks.named<BootWar>("bootWar") {
manifest {
attributes("Main-Class" to "org.springframework.boot loader.PropertiesLauncher")
}
}
4.4.6. 계층화된 Jar 또는 war 패키징(Packaging Layered Jar or War)
기본적으로, bootJar
태스크는 BOOT-INF/classes
및 BOOT-INF/lib
각각에 애플리케이션 클래스와 의존성을 포함한 압축파일을 빌드한다. 마찬가지로, bootWar는 WEB-INF/classes
에 애플리케이션 클래스를 포함하고 WEB-INF/lib
및 WEB-INF/lib-provided
에 의존성을 포함하는 압축파일을 빌드한다. jar 콘텐츠에 도커 이미지를 빌드해야 하는 경우, 이러한 디렉토리를 더 분리하여 각 계층에 쓸 수 있도록 하는 것이 유용하다.
계층화된 jar는 일반 부트 패키지 jar와 동일한 레이아웃을 사용하지만, 각 계층을 설명하는 추가 메타-데이터(meta-data) 파일을 포함한다.
기본적으로, 다음 계층이 정의된다:
• 버전에 SNAPSHOT
이 포함되지 않은 비-프로젝트 의존성에 대한 dependencies
.
• jar 로더 클래스를 위한 spring-boot-loader
• 버전에 SNAPSHOT
이 포함된 비 프로젝트 의존성에 대한 snapshot-dependencies
.
• 프로젝트 의존성, 애플리케이션 클래스 및 리소스에 대한 application
.
계층 순서는 애플리케이션의 일부가 변경될 때 이전 계층이 캐시될 수 있는 가능성을 결정하므로 중요하다. 기본 순서는 dependencies
, spring-boot-loader
, snapshot-dependencies
, application
이다. 변경 가능성이 가장 적은 콘텐츠을 먼저 추가한 다음, 변경 가능성이 높은 계층을 추가해야 한다.
이 기능을 비활성화하려면, 다음과 같은 방법으로 수행할 수 있다:
Groovy
tasks.named("bootJar") {
layered {
enabled = false
}
}
Kotlin
tasks.named<BootJar>("bootJar") {
layered {
isEnabled = false
}
}
계층화된 jar 또는 war가 생성되면, spring-boot-jarmode-layertools
jar가 압축파일에 대한 의존성으로 추가된다. 클래스패스(classpath)에 있는 이 jar 파일을 사용하면, 부트스트랩(bootstrap) 코드가 애플리케이션과 완전히 다른 것(예를들어, 계층을 추출하는 것)을 실행할 수 있는 특수 모드에서 애플리케이션을 시작할 수 있다. 이 의존성을 제외하려면 다음과 같은 방법으로 수행할 수 있다:
Groovy
tasks.named("bootJar") {
layered {
includeLayerTools = false
}
}
Kotlin
tasks.named<BootJar>("bootJar") {
layered {
isIncludeLayerTools = false
}
}
계층 구성 커스텀(Custom Layers Configuration)
애플리케이션에 따라, 계층 생성 방법을 조정하고 새 계층를 추가할 수 있다.
jar 또는 war를 계층으로 분리하는 방법과, 해당 계층의 순서를 설명하는 구성을 사용할 수 있다. 다음 예는 위에서 설명한 기본 순서를 명시적으로 정의하는 방법을 보여준다:
Groovy
tasks.named("bootJar") {
layered {
application {
intoLayer("spring-boot-loader") {
include "org/springframework/boot/loader/**"
}
intoLayer("application")
}
dependencies {
intoLayer("application") {
includeProjectDependencies()
}
intoLayer("snapshot-dependencies") {
include "*:*:*SNAPSHOT"
}
intoLayer("dependencies")
}
layerOrder = ["dependencies", "spring-boot-loader", "snapshot-dependencies", "application"]
}
}
Kotlin
tasks.named<BootJar>("bootJar") {
layered {
application {
intoLayer("spring-boot-loader") {
include("org/springframework/boot/loader/**")
}
intoLayer("application")
}
dependencies {
intoLayer("application") {
includeProjectDependencies()
}
intoLayer("snapshot-dependencies") {
include("*:*:*SNAPSHOT")
}
intoLayer("dependencies")
}
layerOrder = listOf("dependencies", "spring-boot-loader", "snapshot-dependencies", "application")
}
}
layered
DSL은 세 부분으로 정의된다.
• application
클로저(closure)는 애플리케이션 클래스와 리소스가 계층화되는 방식을 정의한다.
• dependencies
클로저는 의존성이 계층화되는 방법을 정의한다.
• layerOrder
메서드는 계층이 작성되어야 하는 순서를 정의한다.
중첩된 intoLayer
클로저는 application
및 dependencies
섹션 내에서 계층에 대한 콘텐츠를 요청하는 데 사용된다. 이러한 클로저는 정의된 것처럼 위아래 순서대로 수행된다. 이전 intoLayer
클로저에 의해 소유권이 선점되지 않은 모든 콘텐츠는 후속 콘텐츠에서 사용할 수 있다. 또한 프로젝트 의존성을 포함하거나 제외하는 데 사용할 수 있는 ncludeProjectDependencies()
및 excludeProjectDependencies()
메서드를 제공한다.
intoLayer
클로저는 중첩된 include
및 exclude
호출하여 콘텐츠를 선점한다. application
클로저는 include/exclude 파라미터에 앤트-스타일(Ant-style) 경로 일치를 사용한다. dependencies
섹션은 group:artifact[:version]
패턴을 사용한다.
include
호출이 수행되지 않으면, 모든 콘텐츠(이전 클로저에서 소유권을 선점하지 않은 콘텐츠)가 고려된다.
exclude
호출이 수행되지 않으면, 제외가 적용되지 않는다.
위의 예에서 dependencies
클로저를 보면, 첫 번째 intoLayer
가 application
계층에 대한 모든 프로젝트 의존성을 요구한다는 것을 알 수 있다. 다음 intoLayer
는 snapshot-dependencies
계층에 대한 모든 SNAPSHOT 의존성을 요구한다. 세 번째이자 마지막 intoLayer
는 dependencies
계층에 대해 남아 있는 모든 항목(이 경우 프로젝트 의존성 또는 SNAPSHOT이 아닌 모든 의존성)을 요구한다.
application
클로저에도 비슷한 규칙이 있다. spring-boot-loader
계층에 대한 org/springframework/boot/loader/**
콘텐츠를 처음 선점하는 것이다. 그런 다음 application
계층에 대한 나머지 클래스 및 리소스를 요청한다.
intoLayer
클로저가 추가되는 순서는 종종 계층이 작성되는 순서와 다르다. 이러한 이유로layerOrder
메소드는 항상 호출되어야 하며intoLayer
호출이 참조하는 모든 계층을 커버해야 한다.