본문 바로가기

Spring Framework/Spring Boot

[Spring Boot] Jar vs War 배포 방법 비교

개요

스프링부트를 사용하는 https://start.spring.io 에 들어가보면

 

Packaging 방식이 Jar과 War 두 가지로 구분되는 것을 확인할 수 있습니다.

 

두 가지 방식의 차이점이 궁금해져서 이 포스트를 시작하게 되었습니다.

 

 


핵심 개념

기본적으로 JAR, WAR 모두 java의 jar 옵션을 이용해 생성된 압축(아카이브)파일이며,

애플리케이션을 쉽게 배포하고 동작시킬 수 있도록 관련 파일(리소스, 속성 파일 등)을 패키징한 것이다.

 

 

1. JAR(Java Archive)

  • Java Application이 동작할 수 있도록 자바 프로젝트를 압축한 파일
  • 라이브러리, 리소스, 보조 파일(property files)을 포함
  • JRE(Java Runtime Enviornment)만 있어도 실행 가능
  • 스프링 부트에서 권장하는 방법

 

2. WAR(Web Apllication Archive)

  • Servlet/JSP 컨테이너에 배치할 수 있는 웹 애플리케이션(Wep Application)을 포함
  • JSP, HTML, JavaScript같은 웹 관련 자원을 포함
  • 별도의 웹 서버(Web Server) or 웹 컨테이너(WAS) 필요
  • 웹 컨테이너나 서버에 종속적이기에 특정한 웹 컨테이너 환경이 필요
  • 배포 및 전달에 있어서 JAR 방식에 비해 크기가 크고 번거로울 수 있다

 

요악하자면
JAR은 독립적인 실행 가능한 애플리케이션을 구성하는 것을 목표
WAR 파일은 웹 환경에서 실행되는 애플리케이션을 구성하는 것을 목표로 한다.

 

 

JAR과 WAR의 파일 구조 등이 궁금하신 분은 하단을 쭉 읽어보시는 것도 좋을 듯 합니다.

 

 


Nested JARs

스프링부트 공식 문서에 따르면 Java에서는 nested jar files를 load하는 표준 방법을 제공하지 않습니다.

그렇기에 스프링 부트에서는 특정 파일 구조를 통해 이를 해결합니다.

 

1. Jar File Structure

example.jar
 |
 +-META-INF
 |  +-MANIFEST.MF
 +-org
 |  +-springframework
 |     +-boot
 |        +-loader
 |           +-<spring boot loader classes>
 +-BOOT-INF
    +-classes
    |  +-mycompany
    |     +-project
    |        +-YourClasses.class
    +-lib
       +-dependency1.jar
       +-dependency2.jar

크게 META-INF, org, BOOT-INF로 구성된 파일 구조를 가집니다.

 

개발자가 직접 작성한 클래스 파일들은 BOOT-INF/classes 디렉터리에 들어가고,

의존성 주입은 BOOT-INF/lib 디렉터리에 들어가야 하는 등 규칙이 존재합니다.

 

 

1-1. classpath.idx

Jar 파일 구조에는 classpath.idx라는 파일도 존재합니다.

이 파일은 BOOT-INF/classpath.idx로 제공되며,

스프링 부트의 Maven과 Gradle build plugins에 의해 자동으로 생성됩니다.

이 파일은 "일반적인 JAR 파일은 중첩된 JAR 구조를 지원하지 않는다"는 단점을 보완하기 위해 스프링 부트가 고안해낸 방법으로, 순서가 정해진 jar 파일 리스트를 확인하여 중첩된 구조를 지원합니다.

 

 

1-2. META-INF

JAR(Java Archive) 파일의 메타 데이터와 구성 정보를 포함하는 디렉터리입니다.

실행 설정, 보안 서명, 서비스 로딩 등 다양한 역할을 수행하지만,

특히, MANIFEST.MF 파일을 통한 애플리케이션의 실행 환경을 정의하는 데 매우 중요한 역할을 합니다.

 

SpringBoot 내에서 MANIFEST.MF

Manifest-Version: 1.0
Implementation-Title: demo
Implementation-Version: 1.0.0
Built-By: user
Built-Date: 2025-01-07

Main-Class: org.springframework.boot.loader.JarLauncher 
//애플리케이션 실행 시 호출될 메인 클래스
Start-Class: com.example.demo.DemoApplication 
//Spring Boot 애플리케이션의 시작 클래스 지정, @SpringBootApplication이 있는 클래스를 가리킴.

Spring-Boot-Version: 3.1.2
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 17
참고!
Maven에서는 pom.xml을 통해, Gradle에서는 build.gradle에서 설정 가능합니다.
설정과 관련된 내용은 찾아보시는 걸 추천드립니다.
참고!
Main-Class가 org.springframework.boot.loader.JarLauncher인 이유는 무엇일까?

: 스프링 부트가 내장 런처를 사용해 애플리케이션을 실행하는 구조를 갖고 있기 때문!

실행 과정
1. java -jar 명령어로 프로그램 실행
2. java -jar 명령어가 Main-Class에 지정된 JarLauncher 호출
3. JarLancher 동작
- BOOT-INF/classes/ 와 BOOT-INF/lib/ 를 클래스로더에 로드
- MANIFEST.MF의 Start-Class를 찾아 실행
4. 최종적으로 Start-Class에 지정된 스프링 부트 애플리케이션의 메인 클래스가 실행됨.

 

 

2. War File Structure

example.war
 |
 +-META-INF
 |  +-MANIFEST.MF
 +-org
 |  +-springframework
 |     +-boot
 |        +-loader
 |           +-<spring boot loader classes>
 +-WEB-INF
    +-classes
    |  +-com
    |     +-mycompany
    |        +-project
    |           +-YourClasses.class
    +-lib
    |  +-dependency1.jar
    |  +-dependency2.jar
    +-lib-provided
       +-servlet-api.jar
       +-dependency3.jar

크게 META-INF, org, WEB-INF로 구성된 파일 구조를 가집니다.

 

META-INF, org 등을 가진다는 점은 Jar과 같습니다만

 

의존관계들은 WEB-INF/lib 디렉터리에 들어가야 하고,

의존관계들 중 내장(embedded) 서버에서 실행될 때 필요하지만,

전통적인 웹 컨테이너에 배포할 때 필요하지 않은 의존관계는 WEB-INF/lib-provided에 배치한다는 특징이 있습니다.

 

 

2-1. META-INF

Jar file처럼 MANIFEST.MF 파일이 존재하며, 그 내용만 조금 다릅니다.

Manifest-Version: 1.0
Implementation-Title: demo-war
Implementation-Version: 1.0.0
Built-By: user
Built-Date: 2025-01-07
Main-Class: org.springframework.boot.loader.WarLauncher
Start-Class: com.example.demo.DemoApplication
Spring-Boot-Version: 3.1.2
Spring-Boot-Classes: WEB-INF/classes/
Spring-Boot-Lib: WEB-INF/lib/
Build-Jdk-Spec: 17

 

 

War 패키징 방식은 외장 WAS가 필요하다고 들었는데,

Spring Boot에서 War 패키징 방식을 택해도 단독으로 실행이 가능한 이유는

Main-Class의 값이 WarLauncher로 설정되어 있기 때문이라고 합니다.

 

기존 컨테이너(Tomcat, Jetty 등)에 배포하여 실행할경우

Main-ClassStart-class는 무시되고, 컨테이너가 WEB-INF/classes와 WEB-INF/lib을 직접 로드합니다.

 

 

 

 

 

 

Reference

https://velog.io/@mooh2jj/JAR-vs-WAR-%EB%B0%B0%ED%8F%AC%EC%9D%98-%EC%B0%A8%EC%9D%B4

https://docs.spring.io/spring-boot/specification/executable-jar/nested-jars.html

https://hye0-log.tistory.com/27