2024. 1. 29. 13:43ㆍProject 해축갤/[시나리오] 인기게시물의 트래픽은 얼마일까?
1. 문제의 시작: 해축갤 인기 게시물 트래픽 추적 중 에러
해외축구갤러리의 인기 게시물 트래픽을 추적하기 위해 Java 프로그램을 개발하고
Gradle 빌드 시스템을 사용하여 빌드한 후 실행했습니다.
그러나 "java.lang.NoClassDefFoundError: org/jsoup/Jsoup"라는 Gradle 빌드 관련 에러가 발생했습니다.
이는 Spring Boot 프로젝트에서는 경험하지 못한 문제였습니다.
Spring Boot 는 jar 파일 빌드시 기본적으로 모든 dependency를 포함하기 때문입니다.
2. Gradle의 기본 동작 이해하기
이 문제를 해결하기 위해 Gradle 커뮤니티의 토론을 참고했습니다.
First of all, the default jar that Gradle produces will only contain the classes compiled from the source code in the project. It would be counterproductive to always include all of the dependencies in that jar because depending on the deployment environment, those dependencies would already be provided. If you were using the “war” plugin, those dependencies would be included in the WEB-INF/lib.
-> Gradle 은 source code 에서만 Compile 된 애들로만 build 한다.
Second, concerning why the Shadow plugin isn’t provided “natively” in Gradle, the Gradle core is intentionally minimal because different people have different ideas about what they need for their build, and there are widely differing needs. The rich plugin ecosystem that Gradle provides and supports makes this possible.
-> shadow가 기본적으로 포함되지 않은 이유는 Gradle 사용자들마다 니즈가 다 다르기 때문이다.
[출처] : https://discuss.gradle.org/t/how-to-include-dependencies-in-jar/19571/4#post_4
💡 Fat-Jar 란?
"Fat-Jar"는 Java 개발에서 사용되는 용어로, 모든 필요한 종속성(dependencies)을 포함하는
실행 가능한 Java 아카이브 파일(JAR)을 지칭합니다. 이러한 파일은 다음과 같은 특징을 가집니다:
1. 독립 실행 가능: Fat-Jar는 단독으로 실행할 수 있으며, 별도의 라이브러리 설치나 종속성 해결 없이 동작합니다.
2. 포함된 종속성: 모든 필요한 라이브러리가 JAR 파일 내에 포함되어 있어, 이동성과 호환성이 높습니다.
3. 배포 용이성: 단일 파일로 되어 있어 배포 및 관리가 용이합니다.
💡 shadow란?
"Shadow"는 주로 Gradle 빌드 시스템에서 사용되는 용어로, Fat-Jar와 유사한 개념입니다.
Shadow 플러그인은 Gradle 프로젝트에서 Fat-Jar를 생성하는 데 사용됩니다.
이 플러그인을 사용하면 다음과 같은 기능을 수행할 수 있습니다:
1. 종속성 포함: 프로젝트와 그 종속성을 하나의 JAR 파일로 결합합니다.
2. 클래스 경로 조정: 실행 시 클래스 경로 문제를 해결하기 위해 내부적으로 경로를 조정합니다.
3. 충돌 방지: 종속성 간의 버전 충돌을 관리하고 해결하는 기능을 제공합니다.
Gradle의 기본적인 JAR 파일 생성 방식은 프로젝트의 Java 소스 코드만,
즉 사용자가 작성한 소스 코드들의 클래스만을 빌드한다는 것을 이해할 수 있습니다.
3. Spring Boot 가 Gradle의 통합
하지만 Spring Boot 는 기본적으로 Fat-Jar를 만듭니다.
이를 가능케 하는 것은 Spring Boot Gradle Plugin 입니다.
Once the spring-boot plugin has been applied to your project it will automatically attempt to rewrite archives to make them executable using the bootRepackage task. You should configure your project to build a jar or war (as appropriate) in the usual way.
[출처] : https://docs.spring.io/spring-boot/docs/1.2.0.M2/reference/html/build-tool-plugins-gradle-plugin.html
💡 executable 이란?
이 파일 하나만으로도 Java 애플리케이션이 독립적으로 실행될 수 있다는 의미입니다.
즉, 애플리케이션을 실행하는 데 필요한 모든 코드, 라이브러리, 리소스가 JAR 파일 안에 포함되어 있어,
추가적인 의존성 설치 없이 어떤 환경에서도 애플리케이션을 실행할 수 있다는 뜻입니다.
공식 문서에 따르면 이 Spring Boot Gradle Plugin 이 프로젝트에 적용되면 bootRepackage 태스크를 통해
필요한 모든 의존성이 포함된 실행 가능한 JAR 파일을 생성합니다.
따라서 Spring Boot 프로젝트에서는 추가 설정 없이도 의존성이 포함된 JAR 파일을 만들 수 있습니다.
Spring Boot 개발자들이 모든 dependency 를 포함하도록 한 이유는 Spring의 공식문서에서 찾아볼 수 있습니다.
Spring Boot provides a quick (and opinionated) way to create a production-ready Spring-based application. It is based on the Spring Framework, favors convention over configuration, and is designed to get you up and running as quickly as possible.
[출처] : https://docs.spring.io/spring-framework/reference/overview.html
💡 Convention Over configuration(COC) 이란?
소프트웨어 설계 패러다임으로, 프레임워크를 사용하는 개발자가 유연성을 잃지 않으면서도
결정해야 할 사항의 수를 줄이고 반복하지 않기(DRY) 원칙을 준수하기 위해 사용
-> 개발자가 최소한의 설정으로도 애플리케이션을 효과적으로 구성하고 실행할 수 있게 한다는 것을 의미
[출처] : https://en.wikipedia.org/wiki/Convention_over_configuration
따라서 Spring Boot의 Gradle 플러그인이 제공하는 실행 가능한 JAR는
이 원칙에 따라 애플리케이션과 그 의존성을 자동으로 포함한다고 볼 수 있습니다.
4. 일반 Gradle 프로젝트에서의 의존성 포함
Spring Boot Gradle 플러그인이 없는 일반 Gradle 프로젝트에서는
'Shadow Plugin'과 같은 추가 플러그인을 사용하여 Java 의존성을 포함시킬 수 있습니다.
plugins {
id 'java'
id 'com.github.johnrengelman.shadow' version '8.1.1' # Gradle 8.x 면 shadow 도 8.x
}
shadowJar {
manifest {
attributes 'Main-Class': 'org.example.MainClass'
}
exclude 'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA'
}
이를 통해 모든 외부 라이브러리와 프로젝트 코드를 포함하는 'Fat JAR' 또는 'Uber JAR'를 생성할 수 있습니다.
명령어는 기존의 ./gradlew clean build 가 아님을 유의해야 합니다.
./gradlew shadowJar
5. 정리
- "java.lang.NoClassDefFoundError: org/jsoup/Jsoup", Dependency 미포함 에러 발생.
- Gradle은 기본적으로 사용자 코드만 포함하기에 생긴 에러.
- Spring Boot Gradle 플러그인을 이용, Spring Boot 프로젝트에서는 의존성 자동 포함.
- 일반 Gradle 프로젝트에서 'Shadow Plugin'을 사용하여 의존성을 포함한 'Fat JAR' 또는 'Uber JAR' 생성 가능.
6. 참고
https://github.com/johnrengelman/shadow
https://imperceptiblethoughts.com/shadow/getting-started/#default-java-groovy-tasks