목차
들어가기 전에
종종 스프링부트를 사용하지 않은 간단한 순수 자바/코틀린 프로젝트를 빌드해서 실행할 일이 있다. 스프링부트를 사용하지 않는 경우에는 bootJar
명령이 없어서 jar
명령으로 빌드할 수밖에 없는데 이렇게 생성된 jar 파일은 java -jar
명령어로 실행해도 제대로 실행되지 않는다.
이러한 현상이 발생하는 이유와 해결법을 정리해두고자 한다.
왜 gradle jar
로 생성한 jar 파일은 실행되지 않을까?
기본적으로 Gradle의 jar
명령은 프로젝트의 소스 파일들을 컴파일한 후 jar 파일의 형태로 압축한다. 그리고 이 과정에서 MANIFEST.MF
파일이 생성되어 jar 파일에 추가된다.
터미널에 jar tf {jar 파일명}
을 입력하면 jar 파일의 내부 구조를 확인해볼 수 있는데 여기서 META-INF
라는 디렉토리 안에 MANIFEST.MF
가 생성되어 있는 것을 볼 수 있다.
그리고 터미널에 unzip -p {jar 파일명} META-INF/MANIFEST.MF
를 입력하면 META-INF
디렉토리에 있는 MANIFEST.MF
의 내용을 볼 수 있다.
만약 bash: unzip: command not found가 출력된다면 centOS 기준으로 sudo yum install unzip
을 입력하여 unzip을 설치한 후 시도하면 된다.
MANIFEST.MF
의 내용을 보면 뭔가 이상하다. 매니페스트 버전만 나와있고 아무런 내용이 없다. bootJar
로 빌드하여 정상적으로 실행되는 스프링부트 jar 파일의 내용과 비교해 보자.
메인 클래스가 무엇인지를 비롯해 여러가지 정보가 담겨있는 것을 확인할 수 있다.
여기까지 내용을 정리해보면 jar 파일을 실행하기 위해선 반드시 매니페스트에 메인 클래스 정보가 있어야 하는데, 그래들의 기본 jar
명령은 단순히 프로젝트의 소스만 자바 바이트 코드로 컴파일하여 처리하기 때문에 실행이 되지 않는다는 사실을 유추할 수 있다.
외부 라이브러리도 추가되지 않는다!
사실 매니페스트만 문제가 되는게 아니다. build.gradle
을 통해 Unirest를 의존성에 추가한 후 jar
명령을 사용하여 jar 파일을 생성한 다음 자바 디컴파일러 툴을 사용하여 내부를 확인해보자.
그런데 디컴파일러로 확인한 jar 파일 내부에는 외부 라이브러리가 전혀 추가되어있지 않다. 이러면 매니페스트에 메인 클래스의 정보가 추가되더라도 외부 라이브러리의 클래스를 사용하는 기능은 동작하지 않는다.
jar 파일에 메인 클래스 정보를 추가해주자
우선 jar 파일이 메인 클래스를 찾지 못하는 문제는 build.gradle
또는 build.gradle.kts
에 아래 내용을 추가하여 해결할 수 있다.
build.gradle
jar {
manifest { attributes 'Main-Class': '{메인 메소드가 포함된 클래스 명}' }
}
build.gradle.kts
tasks.jar {
manifest { attributes["Main-Class"] = "{메인 메소드가 포함된 클래스 명}" }
}
클래스 없이 최상위레벨에 메인 메소드를 작성한 코틀린 프로젝트의 경우에는 메인 메소드가 작성된 파일명 + Kt가 메인클래스 명이 된다. (ex. Main.kt 파일에 메인 메소드를 작성했다면 메인 클래스 명은 MainKt가 된다.)
해당 내용을 적용한 후 jar 파일을 생성하여 MANIFEST.MF
의 내용을 확인해보면 메인 클래스의 경로가 추가되어 있고, java -jar
명령어를 사용하여 정상적으로 파일을 실행할 수도 있다.
외부 라이브러리도 추가해주자
아래와 같이 단순히 Unirest 라이브러리에 존재하는 JSONObject 객체를 생성하는 코드를 추가하고 jar 파일을 생성하여 실행해보자.
import kong.unirest.json.JSONObject
fun main() {
val json = JSONObject()
println("Hello, World!")
}
JSONObject 클래스를 찾을 수 없다고 한다. 앞서 언급한 것처럼 jar 파일 내부에 라이브러리가 없기 때문이다.
jar 파일에 라이브러리를 추가하기 위해 build.gradle
또는 build.gradle.kts
에 아래 내용을 추가한다.
build.gradle
jar {
...
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
build.gradle.kts
tasks.jar {
...
from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
duplicatesStrategy
속성은 jar 파일 생성 과정에서 중복되는 파일을 처리하는 정책을 결정하는 것인데 위 코드에서는 중복된 파일을 무시하는 방향으로 진행했다.
여기까지 진행하고 jar 파일을 생성하여 자바 디컴파일러로 확인해보면 라이브러리가 추가된 것을 확인할 수 있다.
마치며
사실 요즘 대부분의 자바 애플리케이션은 스프링부트를 사용하다보니 실제로 이 글의 내용을 사용할 일은 많지 않다고 생각한다.
하지만 굳이 스프링부트와 같은 프레임워크를 사용할 필요가 없는 가벼운 자바 애플리케이션을 장난삼아(?) 개발해서 빌드할 일이 있다면 유용하지 않을까 싶다.
'Development > etc.' 카테고리의 다른 글
[IntelliJ] Remote JVM Debug를 사용해서 서버를 원격으로 디버깅 하기 (0) | 2024.10.02 |
---|---|
[IntelliJ] 스프링부트 프로젝트의 클래스 또는 리소스 수정 후 자동으로 재시작하기 (2) | 2023.11.02 |
[IntelliJ] 인텔리제이의 Remote host를 사용하여 서버에 접속해보자! (0) | 2023.03.09 |
[Linux] 특정 프로세스를 종료하는 쉘 스크립트 작성하기 (0) | 2023.03.02 |
[IntelliJ] 코드 에디터에 나오는 Usages, Code author 등의 힌트 위치 변경 (0) | 2022.12.01 |
댓글