먼저 JAR와 WAR에 대해 알아보자.
JAR
- Java Archive의 줄임말이다.
- 자바의 클래스 파일과 여러 리소스를 묶어서 만든 압축 파일이다.
- 이 파일은 JVM위에서 실행되거나 또는 다른 곳에서 사용하는 라이브러리로 제공된다.
- 직접 실행하는 경우 main() 메서드가 필요하고, MANIFEST.MF 파일에 실행할 메인 메서드가 있는 클래스를 지정해야 한다.
WAR
- Web Application Archive의 줄임말이다.
- WAR 파일은 WAS에 배포할 때 사용하는 파일이다. 웹 애플리케이션 서버 위에서 동작한다.
- HTML같은 정적 리소스와 클래스 파일을 모두 포함하므로 JAR보다 구조가 복잡하다.
과거와 현재의 WAS 사용 방식
과거에는 스프링으로 웹 애플리케이션을 개발할 때는 먼저 서버에 톰캣과 같은 WAS를 설치해야 했다.
그리고 WAS에서 우리 프로젝트 코드가 동작하도록 WAR 형식으로 프로젝트를 빌드하고 이를 WAS에 전달해서 배포했다.
이런 방식은 WAS 기반 위에서 개발하고 실행하며, IDE 환경에서도 WAS와 연동해서 실행하도록 추가적인 설정이 필요하다.
최근에는 스프링 부트가 내장 톰캣을 포함하고 있다. 즉, 애플리케이션 코드 안에 WAS가 라이브러리로 내장되어 있다.
이와 같은 방식은 코드를 작성하고 JAR로 빌드한 다음에 해당 JAR를 원하는 위치에서 실행하기만 하면 WAS도 함께 실행된다.
과거 방식보다 훨씬 편해졌다.
직접 WAS를 설치해 배포하는 방식부터 스프링 부트를 이용하는 내장 탐캣 배포 방식을 가볍게 살펴본다.
과거 WAS를 직접 설치하고 배포하는 방식
- 먼저 war로 빌드할 스프링 프로젝트를 생성한다.
- https://tomcat.apache.org/download-10.cgi에서 tomcat을 설치한다.
- 프로젝트에서 로직을 수행할 서블릿과 정적 리소스 파일을 작성한다.
- 이후 .war 파일로 빌드를 진행한다.
- 빌드를 진행한 war 파일을 톰캣 서버에 배포하고 실행한다.
이렇게 war 파일을 만들어서, 서버에서 배포해보았다. 배포하는 과정이 너무 번잡하다.
서블릿 컨테이너 초기화
WAS를 실행하는 시점에 필요한 초기화 작업들이 존재한다. 서비스에 필요한 필터와 서블릿을 동작하고,
스프링을 사용하면 스프링 컨테이너를 만들고, 서블릿과 스프링을 연결하는 디스페처 서블릿도 등록해야 한다.
서블릿 초기화
- ServletContainerInitializer라는 인터페이스를 사용해 서블릿 컨테이너를 초기화했다.
- 또한 resources/META-INF/services/jakarta.servlet.ServletContainerInitializer 경로에 초기화할 클래스를 알려준다.
- 그러면 WAS를 실행할 때 해당 클래스를 초기화 클래스로 인식하고 로딩 시점에 실행한다.
애플리케이션 초기화
- 애플리케이션 초기화를 위한 인터페이스를 작성하고 @HandlesTypes 애노테이션을 사용해 애플리케이션 초기화를 진행했다.
- 당연하지만 resources/META-INF/services/jakarta.servlet.ServletContainerInitializer에 초기화할 클래스를 추가했다.
- 애플리케이션 초기화를 만든 이유는 서블릿 코드에 의존하지 않기 위함이다.
스프링 컨테이너 등록
- 스프링 컨테이너를 생성하고, 스프링 컨트롤러를 작성하고 이를 스프링 빈으로 등록했다.
- 생성한 스프링 컨테이너는 디스패처 서블릿 생성자에 파라미터로 넘기며, 스프링과 서블릿을 연결했다.
- 이제 디스패처 서블릿에 HTTP 요청이 오면 디스패처 서블릿은 해당 스프링 컨테이너에 들어있는 컨트롤러 빈들을 호출한다.
- 마지막 방법으로 스프링에서 지원하는 WebApplicationInitializer 인터페이스 구현 클래스를 사용해 손쉽게 서블릿 컨테이너를 초기화했다.
서블릿 컨테이너를 초기화해 서블릿도 등록하고, 스프링 컨테이너도 생성해 등록하고 스프링 MVC가 동작하도록 디스패처 서블릿도 등록해보았다. 위 내용은 항상 탐캣 같은 서블릿 컨테이너에 배포를 해야만 동작하는 방식이다.
사실 이런 부분은 웹 애플리케이션을 개발하는 다양한 스프링 기반 프로젝트에서 공통적으로 작성하는 부분이다.
서블릿 컨테이너를 초기화하고 스프링 컨테이너를 만들고 스프링과 서블릿 컨테이너를 연결하기 위해 디스패처 서블릿을 등록해야 한다.
그러나 스프링 부트와 내장 톰캣을 사용하면서 이런 부분이 바뀌었다.
스프링 부트와 내장 톰캣을 사용하면서 서블릿 컨테이너를 초기화할 필요도 없어졌고 배포도 매우 간단해졌다.
참고로 스프링 부트는 서블릿 컨테이너 초기화를 하지 않아도 되고 tomcat을 중심으로 배포하지 않아 Containerless하다고 불린다.
부트를 사용하면 스프링 컨테이너가 초기화되는 과정 중에 서블릿 컨테이너가 초기화 되며,
내장 톰캣 실행, 스프링 컨테이너 생성, 디스패처 서블릿, 컴포넌트 스캔까지 모든 동작이 한번에 동작한다.
내장 톰캣의 등장
WAR 배포는 WAS를 설치하고 애플리케이션 코드를 WAR로 빌드하고 이를 WAS에 배포해야 한다. 너무 복잡하다.
단순히 자바의 main 메서드처럼 실행하면 웹서버까지 같이 실행할 수는 없을까?
즉 톰캣 같은 웹서버를 라이브러리로 내장해버리는 것이다.
이런 문제를 해결하기 위해 톰캣은 라이브러리로 제공하는 내장 톰캣 기능을 제공한다.
내장 톰캣 사용
- 내장 톰캣 객체를 생성하고, 톰캣이 제공하는 커넥터를 사용해서 8080 포트에 연결하고 서블릿을 등록 후 톰캣을 start()해 사용한다.
- 내장 톰캣 덕분에 복잡한 설정 없이 main 메서드만 실행하면 톰캣까지 매우 편리하게 실행되었다. 톰캣을 설치하지 않아도 된다.
내장 톰캣을 활용한 배포
- 자바의 main() 메서드를 실행하려면 jar 형식으로 빌드해야 한다. 그래서 jar 형식으로 빌드를 해보았다.
- 그러나 오류가 발생한다. jar 파일에는 jar를 포함할 수 없기 때문이다.
- 그래서 fat jar라는 방식을 이용해서 빌드를 진행했다.
- fat jar란 라이브러리에 사용되는 jar를 풀면 나오는 모든 class를 뽑아서 새로 만드는 jar에 포함시키는 방법이다.
fat jar를 사용해서 jar 파일에 필요한 라이브러리를 내장할 수 있게 되었다.
결과적으로 하나의 jar 파일로부터 웹 서버 설치와 실행까지 모든 것을 단순화할 수 있다.
그러나 단점도 존재한다. 모두 class로 풀리니 어떤 라이브러리가 포함되어 있는지 확인하기 어렵고, 파일명 중복을 해결할 수 없다.
스프링 부트의 빌드
스프링 부트는 fat jar의 문제를 해결하기 위해 jar 내부에 jar를 포함할 수 있는 특별한 구조의 jar를 만들고 동시에 만든 jar를 내부 jar를 포함해서 실행할 수 있게 했다. 이것을 실행 가능 Jar라고 한다.
이 실행 가능 jar를 사용하면 파일명 중복 문제, 어떤 라이브러리가 포함되어 있는지 확인하기 어려운 문제를 모두 해결할 수 있다.
참고 자료
댓글