티스토리 뷰
WAR 배포 방식의 단점
웹 애플리케이션을 구동하고 싶으면 WAS를 별도로 설치해야하는 구조이다.
과거에는 WAS와 빌드파일(WAR)이 분리되어 있는 것이 당연한 구조였다.
- 톰캣 같은 WAS를 별도로 설치해야 한다.
- 개발 환경 설정이 복잡하다.
- 단순 자바라면 별도의 설정을 고민하지 않고 main 메서드만 실행하면 된다.
- 웹 애플리케이션은 WAS를 실행하고 또 WAR와 연동하기 위한 복잡한 설정이 들어간다.
- 배포 과정이 복잡하다. 애플리케여선 코드를 WAR로 빌드하고 이것을 또 WAS에 전달해서 배포해야 한다.
- 톰캣 버전을 변경하려면 톰캣을 다시 설치해야 한다.
단순히 자바의 main 메서드만 실행하면 웹 서버까지 같이 실행되도록 고민한 사람들이 있고 톰캣도 자바로 만들어져 있으니 톰캣을 라이브러리처럼 포함해서 사용하기 위해 웹서버를 라이브러리로 내장했다.
내장 톰캣 사용
톰캣을 라이브러리로 포함하고 자바 코드로 실행해 보자
톰캣 설정
- 내장 톰캣을 생성하고, 톰캣이 제공하는 커넥터를 이용해서 8080포트에 연결한다.
서블릿 등록
- 톰캣에 사용할 contextPath와 docBase를 지정한다.
- tomcat.addServlet()을 통해서 서블릿을 등록한다.
- context.addServletMappingDecoded() 통해서 등록한 서블릿의 경로를 맵핑한다.
톰캣 시작
- tomcat.start() 로 톰캣을 시작한다.
스프링 컨테이너 연결
톰캣 설정
- 내장 톰캣을 생성하고, 톰캣이 제공하는 커넥터를 이용해서 8080포트에 연결한다.
스프링 컨테이너 생성
- 스프링 컨테이너를 만들고 필요한 빈을 등록한다.
- 애노테이션 기반으로 빈을 생성하는 스프링 컨테이너 AnnotationConfigWebApplicationContext
- 스프링 MVC 디스패쳐 서블릿을 만들고 스프링 컨테이너에 연결한다.
서블릿 등록
- 톰캣에 사용할 contextPath와 docBase를 지정한다.
- tomcat.addServlet()을 통해서 디스패쳐 서블릿을 등록한다.
- context.addServletMappingDecoded() 통해서 등록한 디스패쳐 서블릿의 경로를 맵핑한다.
톰캣 시작
- tomcat.start() 로 톰캣을 시작한다.
내장 톰캣 빌드와 배포 1
내장 톰캣으로 어떻게 빌드하고 배포하는지 알아본다.
자바의 main 메서드를 실행하기 위해서는 jar 형식으로 빌드해야한다.
그리고 jar 안에는 META-INF/MANIFEST.MF 파일에 실행할 main 메서드의 클래스를 지정해 주어야 한다.
MANIFEST.MF 파일은 직접만들지 않고 gradle의 도움을 받으면 쉽게 생성할 수 있다.
./gradlew clean buildJar 실행하여 jar파일로 만들어질 때 컴파일시 error: unmappable character (0xEB) for encoding x-windows-949 오류가 나서 추가한
compileJava { options.encoding = 'utf-8 }
생성된 jar 파일을 풀어보면 lib jar들이 포함이 안되었기 때문에 실행이 되지 않는다. spring container, tomcat을 찾을 수 없어서
jar파일은 jar 파일을 포함할 수 없다.
WAR와 다르게 JAR 파일은 내부에 라이브러리 역할을 하는 JAR 파일을 포함할 수 없다. 포함한다고 해도 인식이 안된다. 이것이 JAR 파일 스펙의 한계이다. WAR는 WAS 위에서만 실행되기 때문에 현재 내장 WAS로 만들어진 프로젝트에는 WAR를 사용할 수 없다.
대안으로 라이브러리 jar 파일을 모두 구해서 MANIFEST 파일에 해당 경로를 적어주면 인식이 되지만 매우 번거롭고, jar 파일 안에 jar 파일을 포함할 수 없기 때문에 라이브러리 역할을하는 jar 파일도 항상 함께 가지고 다녀야 한다. 이 방법은 권장하지 않는다.
대안으로 fat jar 또는 uber jar 라고 불리는 방법이 있다.
jar 안에 jar를 포함할 수 없지만 클래스는 얼마든지 포함할 수 있다.
라이브러리에 사용되는 jar를 풀면 .class파일들이 나온다. 이 클래스 파일을 뽑아서 새로 만드는 jar에 포함하는 것.
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
해당 부분이 라이브러리들을 반복문으로 돌리며 jar 파일에 클래스들을 뽑아 새로 만드는 jar에 넣어준다.
Fat Jar 장점
- Fat Jar 덕분에 하나의 jar 파일에 필요한 라이브러리들을 내장할 수 있게 되었다.
- 내장 톰캣 라이브러리를 jar 내부에 내장할 수 있게 되었다.
- 덕분에 하나의 jar 파일로 배포부터, 웹 서버 설치 + 실행까지 모든 것을 단순화 할 수 있다.
WAR 단점과 해결
- WAS를 별도로 설치하지 않아도 된다. 내장 WAS가 라이브러리로 jar 내부에 포함되어있다.
- 단순한 자바라면 별도의 설정을 고민하지 않고 main 메서드만 실행하면된다. IDE에 복잡한 WAS 설정이 필요하지 않다.
- 배포 과정이 단순하다. JAR를 만들고 이것을 원하는 위치에서 실행만 하면 된다.
- 톰캣 버전을 변경하려면 gradle에서 내장 톰캣 라이브러리 버전만 변경하고 빌드 후 실행하면 된다.
Far Jar 단점
- 어떤 라이브러리가 포함되었는지 확인하기 어렵다
- 모두 class로 풀려있으니 어떤 라이브러리가 사용되고 있는지 추적하기 어렵다.
- 파일명 중복을 해결할 수 없다.
- 클래스나 리소스명이 같은 경우 하나를 포기해야 한다. 이것은 심각한 문제를 발생한다.
- META-INF/services/jakarta.servlet.ServletContainerInitializer 이 파일이 여러 라이브러리(jar)에 있을 수 있다.
- 'A' 라이브러리와 'B' 라이브러리 둘 다 해당 파일을 사용해서 서블릿 컨테이너 초기화를 시도한다. 둘다 해당 파일을 jar 안에 포한한다.
- Fat Jar를 만들면 파일명이 같으므로 'A', 'B' 라이브러리가 둘다 가지고 있는 파일 중에 하나의 파일만 선택된다. 결과적으로 나머지 하나는 포함되지 않으므로 정상 동작하지 않는다.
스프링 부트에서는 이러한 단점까지 보완을 해놨다.
간단한 스프링 부트 프로젝트를 만들어보면
configClas : 스프링 설정 객체를 파라미터로 전달 받는다.
args : main(arags) 배열을 전달 받는다.
tomcat.start() : 이번에는 예외를 try/catch로 런타임 예외로 변경했다.
MySpringBootMain의 위치가 클래스들이 담긴 패키지의 최상의 클래스인 이유는 @ComponentScan을 가진 클래스는 해당 패키지부터 그 하위까지 모든 클래스를 읽으며 @Component 를 가진 클래스를 찾아서 Bean으로 등록해준다.
지금까지 만든 것을 라이브러리로 만들어 배포하면 그것이 스프링 부트이다.
스프링 부트와 웹 서버 - 프로젝트 생성
스프링 부트는 지금까지 고민한 문제를 깔끔하게 해결해준다.
- 내장 톰캣을 사용해서 빌드와 배포를 편리하게 한다.
- 빌드시 하나의 Jar를 사용하면서 동시에 Fat Jar 문제도 해결한다.
- 지금까지 진행한 내장 톰캣 서버를 실행하기 위한 복잡한 과정을 모두 자동으로 처리한다.
스프링 부트와 웹 서버 - 실행 과정
스프링 부트를 실행할 때는 자바 main 메서드에서 SpringApplication.run() 을 호출하면 된다.
여기에 메인 설정 정보를 넘겨주는데 보통 @SpringBootApplication 애노테이션이 있는 현재 클래스를 지정해주면 된다.
참고로 현재 클래스에는 @SpringBootApplication 애노테이션이 있는데, 이 애노테이션 안에는 컴포넌트 스캔을 포함한 여러 기능이 설정되어 있다. 기본 설정은 현재 패키지와 그 하위 패키지 모두를 컴포넌트 스캔한다.
해당 구문안에 핵심은 2가지이다.
- 스프링 컨테이너를 생성한다.
- WAS를 생성한다.
스프링 부트와 웹 서버 - 빌드와 배포
실제 동작하는 build 파일인 boot-0.0.1-SNAPSHOT.jar 파일을 압축 풀어보면
Main-Class로 BootApplication 클래스가 아닌 JarLauncher가 들어가 있고
Start-Class에 BootApplication이 들어가 있다.
WAR와 다르게 JAR 파일은 내부에 라이브러리 역할을 하는 JAR 파일을 포함할 수 없다. 포함한다고 해도 인식이 안된다.
이렇다고 했는데 Library jar 파일들이 들어가 있다.
스프링 부트 실행 가능 Jar
스프링 부트는 Fat Jar와 같은 문제를 해결하기 위해 jar 내부에 jar를 포함할 수 있는 자신만의 특별한 구조의 jar를 만들고 동시에 만든 jar 내부 jar를 포함해서 실행할 수 있게 했다. 이것을 실행가능 Jar(Executable Jar)라 한다. 이 실행가능 Jar를 사용하면 Fat Jar의 문제를 해결할 수 있다,
- jar 내부에 jar를 포함하기 때문에 어떤 라이브러리가 포함되어 있는지 쉽게 확인할 수 있다.
- jar 내부에 jar를 포함하기 때문에 a.jar, b.jar 내부에 같은 결로의 파일이 있어도 둘 다 인식할 수 있다.
참고로 실행 가능 Jar는 자바 표준은 아니고 스프링 부트에서 새롭게 정의한 것이다.
- META-INF 폴더 : jar 파일은 MANIFEST.MF 파일을 통해 메인메서드 찾아서 실행하는 규격을 지녔다.
- loader 폴더 : org/springframework/boot/loader 하위에 있는 클래스들이다. jar 안에 jar를 실행하기 위한 특별 폴더로 스프링 부트의 main 메서드가 실행되는 클래스인 JarLauncher.class가 들어있고 JarLauncher를 포함한 스프링 부트가 제공하는 실행 가능 Jar를 실제로 구동시키는 클래스들이 포함되어 있다. 스프링 부트는 빌드시에 이 클래스들을 포함해서 만들어 준다.
- BOOT-INF : 사용자 클래스와 라이브러리를 포함하고있다. WAR 파일 압축버전의 WEB-INF 라고 생각하면 된다. JarLuancher를 통해 여기있는 classes와 lib에 있는 jar파일들을 읽어들인다.
- classes : 우리가 개발한 class파일과 리소스 파일이 있는 곳
- lib : 외부 라이브러리
- classpath.idx : lib 폴더에 있는 외부 라이브러리가 어떤게 있는지 정리된 정보
- layers.idx : 스프링 부트 구조 정보
java -jar 프로젝트.jar 명령어로 압축된 jar파일을 실행하면 META-INF/MANIFEST.MF 파일을 먼저 찾는다.
해당 파일안에 Main-Class를 읽어서 main 메서드를 실행하게 된다.
- Main-Class : 우리가 기대한 main 메서드가 있는 hello.boot.BootApplication이 아니라 JarLauncher 라는 전혀 다른 클래스를 실행하고 있다. JarLauncher는 스프링 부트가 빌드시 넣어준다. org/springframework/boot/loader/JarLauncher에 실제로 포함되어 있다. 스프링 부트는 jar 내부에 jar를 읽는 기능이 필요하고 특별한 구조에 맞게 클래스 정보도 읽어야 하는데 이러한 일들을 JarLauncher가 처리해준다. 이 작업을 먼저 처리 후 Start-Class에 지정된 main 메서드를 호출한다.
- Start-Class : 원래 우리가 알고 있던 main메서드가 실행해야 하는 클래스가 적혀있다.
- 기타 스프링 부트가 내부에서 사용하는 정보들
- Spring-Boot-Version : 스프링 부트 버전
- Spring-Boot-Classes : 개발한 클래스 경로
- Spring-Boot-Lib : 라이브러리 경로
- Spring-Boot-Classpath-Index : 외부 라이브러리 모음
- Spring-Boot-Layers-Index : 스프링 부트 구조 정보
- 참고 Main-Class를 제외한 나머지는 자바 표준이 아니다. 스프링 부트가 임의로 사용하는 정보이다.
실행과정 정리
- java -jar 프로젝트.jar
- MANIFEST.MF 인식
- JarLauncher.main() 실행
- BOOT-INF/classes/ 인식
- BOOT-INF/lib/인식
- BootApplication.main() 실행
'자바 > 스프링 부트' 카테고리의 다른 글
자동 구성 Auto Configuration (0) | 2023.03.19 |
---|---|
스프링 부트 스타터와 라이브러리 관리 (0) | 2023.03.16 |
웹 서버와 서블릿 컨테이너 (0) | 2023.03.11 |
스프링 부트소개 (0) | 2023.03.07 |
섹션10. 스프링 부트 자세히 살펴보기 (0) | 2023.03.01 |