본문 바로가기
320x100
320x100

우선 프로젝트를 만들때 Spring initializer로 만들면 IDE가 배포 패키징(아카이빙)을 WAR로 할지, JAR로 할지 선택하라고 한다

그리고 의존성 관리는 Maven(Pom.xml)로 할건지, Gradle(build.gradle-Groovy)로 할건지도 선택하게 된다

일단 이 글에서는 Maven/Gradle, War/Jar의 차이보다는

전반적인 패키징에 대한 것, 그리고 plain-jar, not plain-jar가 무엇인지에 대해서 포스팅해보려고 한다

 

일단 나 그리고 우리 회사는 로컬에서 빌드 후 파일통신으로 jar를 올려서 돌리기때문에 서버에서 터미널로 빌드를 하지는 않는다

그리고 로컬에서 빌드할때는 빌드 Task의 build를 이용하기보단 bootJar를 이용한다(IntelliJ)

이제 이 차이를 알아보자

일단 최신버전의 스프링부트에서는 빌드시 다른 옵션을 주지 않고서는 프로젝트 이름-버전.jar / 프로젝트 이름-버전-plain.jar 이렇게 두개의 파일이 만들어진다

한번 확인해보자

프로젝트 트리

인텔리제이 빌드 옵션 탭에서 Build Project를 눌러주자

그럼 build폴더가 생기는 것을 볼 수 있다

어..? 어느 블로그 글 보니까 lib 폴더가 있다던데...

이건 intelliJ가 해주는 빌드다. 인텔리제이가 Gradle의 Build Task를 모두 해주는 것은 아니다!

인텔리제이는 JPS라고 불리는 시스템을 통해 수정된 것만 빌드하고 assemble까지만 한다고 한다

아래는 Gradle의 빌드 Task이다

자 그럼, build 폴더를 지우고 다시 Gradle 탭을 눌러서 build->build를 눌러보자

Gradle 탭의 위치

여기에서 실행된 Task목록들을 볼 수 있다

아까보단 시간이 조금 오래 걸렸다(그래봐야 x초)

그리고 생성된 폴더들도 많아졌다

우리의 목적은 libs내부의 jar파일

자 여기서 우리가 원하는 jar는 뭘까

정답을 미리 말하면 -plain이라고 명명된 파일이 아닌 [프로젝트이름]-버전-스냅샷.jar이다

이 프로젝트를 java -jar xxx.jar로 실행하면 아주 잘 실행될 것이다

 

하지만 이 글을 쓰는 이유는 이런 간단한걸 적기 위해 쓴게 아니다~!

일단 빌드할때 생성되는 war, jar, ear등은 Archive라고 한다

컴파일을 거쳐서 모인 class나 리소스파일등을 다른 트리 형태로(war/jar) 압축한 것을 그냥 있어보이게 Archive(아카이브)라고 부르는 것이다

JVM이 JRE에 올려서 classpath를 찾아서 실행시켜주는 것이다

 

그럼 plain은 무엇인가?

plain.jar는 Plain Archive라고 하며, 어플리케이션에 필요한 모든 의존성을 포함하지 않고 작성된 소스코드의 클래스파일과 리소스파일만 포함한다

그리고 기본적으로 메인함수의 classpath를 manifest에 명시해주지 않아서 바로 실행할 수 없다

Plain jar의 또 다른 이름은 Thin jar, Standard jar, Not excutable jar라고 불린다

 

그리고 plain이 붙지 않은 일반 이름의 jar의 다른 이름은

Executable Archive이며, Fat jar, Boot jar, Executable jar, Uber jar라고 부른다

uber는 독일어로 above, over라는 뜻을 가지고 있고, plain을 넘어선이라고 해석하면 된다

그리고 이 jar는 어플리케이션을 실행하기 위한 모든 의존성을 함께 빌드한다

이 파일은 java -jar xxx.jar 로 실행이 가능하다

 

자 그럼 이 Thin, Plain, Fat 이런건 어디서 나온걸까

출처: https://www.quora.com/Why-do-we-call-Spring-Boot-jars-as-Fat-jars-What-is-a-Fat-jar

이 그림을 보면 Thin jar로도 실행이 되야할것 같은데, 내가 짐작하기로는 mainClass를 manifest에 자동으로 적어주지 않기때문에 실행이 되지 않을뿐, java 자체로는 실행이 되는 것 같다

결국 서비스를 만들기 위해서는 Thin/Plain jar는 의미가 없긴 하다

그리고 저 출처에서는 이 그림 아래에 이 말이 적혀있다

Spring Boot popularized this approach to packaging so that running the app becomes a hassle free task.

해석해보면 Spring Boot는 이러한 패키징 접근방법을 대중화하여 앱 실행이 번거롭지 않은 작업이 되도록 했다고 한다

 

그리고 이제 기타 블로그들에서도 볼 수 있는 빌드 시 plain-jar를 생성하지 않는 방법을 알려주겠다

build.gradle에 이렇게 적어주면 된다

jar {
    enabled = false
}

혹은 Gradle -> build 탭에서 bootJar를 클릭하면 된다

bootJar(FatJar)의 태스크는 전체 빌드보다 간소화되었지만 실행에 필요한 모든 작업을 처리해서 아카이빙 파일을 만들어준다

그리고 build/libs 폴더에 봐도 plain(thin) jar가 아닌 boot(fat) jar가 생성된 것을 볼 수 있다

 

그럼! Gradle 태스크에서 jar를 실행해주면 어떻게 될까?

build폴더를 삭제후 테스트해본다

jar는 이런 실행 태스크가 실행되며

plain(thin) jar 가 생성된것을 볼 수 있다

그럼 우린 이제 이렇게 생각하면 된다

jar {
    enabled = false
}

이 코드를 의미 없이 사용했던 분들이 있다면 알아가면 좋겠다!

jar Task는 plain/thin 아카이빙을 만드는 작업이고, bootJar Task는 uber/fat/boot/executable 아카이빙을 만드는 작업이다

그럼 이 스크립트는 jar를 비활성화하겠다는 옵션이다

항상 의존성 스크립트를 고친 후에는 새로고침을 해서 의존성을 업데이트 해주고 실행하자

이번에는 build.gradle에 저 스크립트를 적어주고 jar태스크를 해본다

어..? jar 가 스킵되었다고 나왔다 그럼 build 폴더를 살펴보자

libs폴더가 생성이 되지 않은 것을 볼 수 있다

 

그리고 내 궁금증이 허락하지 않아서 fatJar가 무엇인지 찾아봤다

8년전의 스택오버플로우에서 글을 봤는데 아마 추측컨대 예전 스프링 or 부트에서는 fatJar를 만들려면 이렇게 했었나보다

task fatJar(type: Jar) {
    manifest {
        attributes 'Main-Class': 'com.example.Main'
    }
    baseName = project.name + '-all'
    from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
    with jar
}

이건 삼천포긴한데.. 최근에 AWS를 위한 패키징을 업로드하는 작업중에 AWS에서 특정 서비스는 jar/war대신 .zip만을 받는 곳도 있었다

build.gradle 태스크에서 빌드할때 zip으로 파일을 만드는 스크립트도 있다(이건 심심하면 찾아보시길)

 

그리고 추가로 빌드할때 테스트를 실행하고 또 돌리는데, bootJar로 말아서 파일을 만드는 경우가 아니라면(build를 직접 하는 경우)

이 테스트를 실행하는 작업에서 빌드는 엄청 많은 시간을 소요하게 된다

이 작업을 제외하기 위해선

gradle build --exclude-task test

이 커맨드로 빌드를 하던가, build.gradle 에

test {
    exclude '**/*'
}

또는 wildcard를 이용해서 특정 패턴을 가진 클래스만을 제외할 수 있다

test {
    exclude 'com/example/demo**'
}

 

헥헥 나름 길게 썼다...

우리가 알아본 것은 IDEA Build/Gradle Build의 차이점

plain / Fat Jar의 차이점

등등을 알아봤다

 

다음에는 WAR/JAR/EAR의 차이점이나 Gradle/Gradlew(wrapper)

에 대해 짧막하게 적어볼까 한다

320x100

댓글