1. 실행가능한 압축파일 패키징(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)

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이 추가로 구성된다. 이렇게 하면 bootJarjar 또는 bootWarwar가 서로 다른 출력 위치를 갖게 되어 실행 가능한 압축파일과 일반 압축파일을 동시에 빌드할 수 있다.

만약 일반 압축파일이 아닌 실행 가능한 압축파일에 분류자를 사용하는 것을 선호하는 경우, jarbootJar 태스크에 대해 다음 예제에 표시된 대로 분류자를 구성한다:

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)

BootJarBootWar 태스크는 각각 그레이들의 JarWar 태스크의 하위 클래스이다. 결과적으로, 모든 표준 구성 옵션은 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 접미사가 추가된 코트린 클래스의 이름이다. 예를 들어,ExampleApplicationExampleApplicationKt가 된다. @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/classesBOOT-INF/lib 각각에 애플리케이션 클래스와 의존성을 포함한 압축파일을 빌드한다. 마찬가지로, bootWar는 WEB-INF/classes에 애플리케이션 클래스를 포함하고 WEB-INF/libWEB-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 클로저는 applicationdependencies 섹션 내에서 계층에 대한 콘텐츠를 요청하는 데 사용된다. 이러한 클로저는 정의된 것처럼 위아래 순서대로 수행된다. 이전 intoLayer 클로저에 의해 소유권이 선점되지 않은 모든 콘텐츠는 후속 콘텐츠에서 사용할 수 있다. 또한 프로젝트 의존성을 포함하거나 제외하는 데 사용할 수 있는 ncludeProjectDependencies()excludeProjectDependencies() 메서드를 제공한다.

intoLayer 클로저는 중첩된 includeexclude 호출하여 콘텐츠를 선점한다. application 클로저는 include/exclude 파라미터에 앤트-스타일(Ant-style) 경로 일치를 사용한다. dependencies 섹션은 group:artifact[:version] 패턴을 사용한다.

include 호출이 수행되지 않으면, 모든 콘텐츠(이전 클로저에서 소유권을 선점하지 않은 콘텐츠)가 고려된다.

exclude 호출이 수행되지 않으면, 제외가 적용되지 않는다.

위의 예에서 dependencies 클로저를 보면, 첫 번째 intoLayerapplication 계층에 대한 모든 프로젝트 의존성을 요구한다는 것을 알 수 있다. 다음 intoLayersnapshot-dependencies 계층에 대한 모든 SNAPSHOT 의존성을 요구한다. 세 번째이자 마지막 intoLayerdependencies 계층에 대해 남아 있는 모든 항목(이 경우 프로젝트 의존성 또는 SNAPSHOT이 아닌 모든 의존성)을 요구한다.

application 클로저에도 비슷한 규칙이 있다. spring-boot-loader 계층에 대한 org/springframework/boot/loader/** 콘텐츠를 처음 선점하는 것이다. 그런 다음 application 계층에 대한 나머지 클래스 및 리소스를 요청한다.

intoLayer 클로저가 추가되는 순서는 종종 계층이 작성되는 순서와 다르다. 이러한 이유로 layerOrder 메소드는 항상 호출되어야 하며 intoLayer 호출이 참조하는 모든 계층을 커버해야 한다.