설정
build.gradle
plugins {
id 'java'
}
group = 'hello'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
repositories {
mavenCentral()
}
dependencies {
//스프링 MVC 추가
implementation 'org.springframework:spring-webmvc:6.0.4'
//내장 톰켓 추가
implementation 'org.apache.tomcat.embed:tomcat-embed-core:10.1.5'
}
tasks.named('test') {
useJUnitPlatform()
}
//일반 Jar 생성
task buildJar(type: Jar) {
manifest {
attributes 'Main-Class': 'hello.embed.EmbedTomcatSpringMain'
}
with jar
}
//Fat Jar 생성
task buildFatJar(type: Jar) {
manifest {
attributes 'Main-Class': 'hello.embed.EmbedTomcatSpringMain'
}
duplicatesStrategy = DuplicatesStrategy.WARN
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
with jar
}
- tomcat-embed-core : 톰캣 라이브러리이다. 톰캣을 라이브러리로 포함해서 톰캣 서버를 자바 코드로 실행할 수 있다.
- 서블릿 관련 코드도 포함하고 있다.
- buildJar , buildFatJar 관련된 부분은 뒤에서 다시 설명한다.
서블릿
EmbedTomcatServletMain
public class EmbedTomcatServletMain {
public static void main(String[] args) throws LifecycleException {
System.out.println("EmbedTomcatServletMain.main");
//톰켓 설정
Tomcat tomcat = new Tomcat();
Connector connector = new Connector();
connector.setPort(8080);
tomcat.setConnector(connector);
//서블릿 등록
Context context = tomcat.addContext("", "/");
tomcat.addServlet("", "helloServlet", new HelloServlet());
context.addServletMappingDecoded("/hello-servlet", "helloServlet");
tomcat.start();
}
}
- 톰캣 설정
- 내장 톰캣을 생성하고, 톰캣이 제공하는 커넥터를 사용해서 8080 포트에 연결한다.
- 서블릿 등록
- 톰캣에 사용할 contextPath와docBase를 지정해야 한다. 이 부분은 크게 중요하지 않으므로 위 코드와 같이 적용하자.
- tomcat.addServlet()을 통해서 서블릿을 등록한다.
- context.addServletMappingDecoded()을 통해서 등록한 서블릿의 경로를 매핑한다.
- 톰캣 시작
- tomcat.start() 코드를 사용해서 톰캣을 시작한다.
참고
내장 톰캣을 개발자가 직접 다룰일은 거의 없다. 스프링 부트에서 내장 톰캣 관련된 부분을 거의 대부분 자동화해서 제공하기 때문에 내장 톰캣을 깊이 있게 학습하는 것은 권장하지 않는다
내장 톰캣이 어떤 방식으로 동작하는지 그 원리를 대략 이해하는 정도면 충분하다.
스프링
public class EmbedTomcatSpringMain {
public static void main(String[] args) throws LifecycleException {
System.out.println("EmbedTomcatSpringMain.main");
//톰켓 설정
Tomcat tomcat = new Tomcat();
Connector connector = new Connector();
connector.setPort(8080);
tomcat.setConnector(connector);
//스프링 컨테이너 생성
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
appContext.register(HelloConfig.class);
//스프링 MVC 디스패처 서블릿 생성, 스프링 컨테이너 연결
DispatcherServlet dispatcher = new DispatcherServlet(appContext);
//디스패처 서블릿 등록
Context context = tomcat.addContext("", "/");
tomcat.addServlet("", "dispatcher", dispatcher);
context.addServletMappingDecoded("/", "dispatcher");
tomcat.start();
}
}
- 스프링 컨테이너를 생성하고, 내장 톰캣에 디스패처 서블릿을 등록했다.
- main() 메서드를 실행하면 다음과 같이 동작한다.
- 내장 톰캣을 생성해서 8080 포트로 연결하도록 설정한다.
- 스프링 컨테이너를 만들고 필요한 빈을 등록한다.
- 스프링 MVC 디스패처 서블릿을 만들고 앞서 만든 스프링 컨테이너에 연결한다.
- 디스패처 서블릿을 내장 톰캣에 등록한다.
- 내장 톰캣을 실행한다.
코드를 보면 알겠지만, 서블릿 컨테이너 초기화와 거의 같은 코드이다.
다만 시작점이 개발자가 main() 메서드를 직접 실행하는가, 서블릿 컨테이너가 제공하는 초기화 메서드를 통해서 실행하는가의 차이가 있을 뿐이다.
빌드와 배포
자바의 main() 메서드를 실행하기 위해서는 jar 형식으로 빌드해야 한다.
그리고 jar 안에는 META-INF/MANIFEST.MF 파일에 실행할 main() 메서드의 클래스를 지정해주어야 한다.
META-INF/MANIFEST.MF
Manifest-Version: 1.0
Main-Class: hello.embed.EmbedTomcatSpringMain
gradle 도움을 받으면 이 과정을 쉽게 진행할 수 있다.
task buildJar(type: Jar) {
manifest {
attributes 'Main-Class': 'hello.embed.EmbedTomcatSpringMain'
}
with jar
}
jar 빌드
./gradlew clean buildJar
build/libs 하위에 "embed-0.0.1-SNAPSHOT.jar" 파일이 생성된다. 이를 "java -jar embed-0.0.1-SNAPSHOT.jar" 명령어로 실행하자
java -jar embed-0.0.1-SNAPSHOT.jar

- 오류가 발생한다.(스프링 관련 클래스를 찾을 수 없다는 오류이다)
오류 발생 원인 파악
jar 압축 해제(jar -xvf embed-0.0.1-SNAPSHOT.jar)
jar -xvf embed-0.0.1-SNAPSHOT.jar

- 스프링 라이브러리, 내장 톰켓 라이브러리가 보이지 않는다.
- war 같은 경우 lib에 jar 라이브러리가 포함되어 있었지만, jar는 포함되지 않는다는 것을 알 수 있다.
jar 파일은 jar파일을 포함할 수 없다.
- WAR와 다르게 JAR 파일은 내부에 라이브러리 역할을 하는 JAR 파일을 포함할 수 없다. 포함한다고 해도 인식이 안된다. 이것이 JAR 파일 스펙의 한계이다. 그렇다고 WAR를 사용할 수 도 없다. WAR는 웹 애플리케이션 서버(WAS) 위에서만 실행할 수 있다.
- 대안으로는 라이브러리 jar 파일을 모두 구해서 MANIFEST 파일에 해당 경로를 적어주면 인식이 되지만 매우 번거롭고, Jar 파일안에 Jar 파일을 포함할 수 없기 때문에 라이브러리 역할을 하는 jar 파일도 항상 함께 가지고 다녀야 한다. 이 방법은 권장하기 않기 때문에 따로 설명하지 않는다.
해결 과정
FatJar
//Fat Jar 생성
task buildFatJar(type: Jar) {
manifest {
attributes 'Main-Class': 'hello.embed.EmbedTomcatSpringMain'
}
duplicatesStrategy = DuplicatesStrategy.WARN
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
with jar
}
- 대안으로는 fat jar 또는 uber jar 라고 불리는 방법이다.
- Jar 안에는 Jar를 포함할 수 없다. 하지만 클래스는 얼마든지 포함할 수 있다.
- 라이브러리에 사용되는 jar 를 풀면 class 들이 나온다. 이 class 를 뽑아서 새로 만드는 jar 에 포함하는 것이다.
- 이렇게 하면 수 많은 라이브러리에서 나오는 class 때문에 뚱뚱한(fat) jar 가 탄생한다. 그래서 Fat Jar 라고 부르는 것이다.
jar 빌드
./gradlew clean buildFatJar
build/libs/embed-0.0.1-SNAPSHOT.jar 파일이 생성되는 것을 볼수 있다. 이를 실행해 보자
java -jar embed-0.0.1-SNAPSHOT.jar
정상 동작 하는 것을 확인할 수 있다. embed-0.0.1-SNAPSHOT.jar 파일이 어떻게 생긴 지 확인해 보자
jar -xvf embed-0.0.1-SNAPSHOT.jar
- 해당 파일을 열어보면 여러 라이브러리가 풀려 해당 폴더에 포함되어 있는 걸 알 수 있다.
FatJar 장점
- Fat Jar 덕분에 하나의 jar 파일에 필요한 라이브러리들을 내장할 수 있게 되었다.
- 내장 톰캣 라이브러리를 jar 내부에 내장할 수 있게 되었다.
- 덕분에 하나의 jar 파일로 배포부터, 웹 서버 설치+실행까지 모든 것을 단순화 할 수 있다.
WAR 단점과 해결
- 톰캣 같은 WAS를 별도로 설치해야 한다.
- 해결: WAS를 별도로 설치하지 않아도 된다. 톰캣 같은 WAS가 라이브러리로 jar 내부에 포함되어 있다.
- 개발 환경 설정이 복잡하다.
- 단순한 자바라면 별도의 설정을 고민하지 않고, main() 메서드만 실행하면 된다.
- 웹 애플리케이션은 WAS를 연동하기 위한 복잡한 설정이 들어간다.
- 해결: IDE에 복잡한 WAS 설정이 필요하지 않다. 단순히 main() 메서드만 실행하면 된다.
- 배포 과정이 복잡하다. WAR를 만들고 이것을 또 WAS에 전달해서 배포해야 한다.
- 해결: 배포 과정이 단순하다. JAR를 만들고 이것을 원하는 위치에서 실행만 하면 된다.
- 톰캣의 버전을 업데이트 하려면 톰캣을 다시 설치해야 한다.
- 해결: gradle에서 내장 톰캣 라이브러리 버전만 변경하고 빌드 후 실행하면 된다.
Fat Jar의 단점
- 어떤 라이브러리가 포함되어 있는지 확인하기 어렵다.
- 모두 class 로 풀려있으니 어떤 라이브러리가 사용되고 있는지 추적하기 어렵다.
- 파일명 중복을 해결할 수 없다.
- 클래스나 리소스 명이 같은 경우 하나를 포기해야 한다. 이것은 심각한 문제를 발생한다.
- 예를 들어서 서블릿 컨테이너 초기화에서 학습한 부분을 떠올려 보자.
- META-INF/services/jakarta.servlet.ServletContainerInitializer 이 파일이 여러 라이브러리( jar )에 있을 수 있다. A 라이브러리와 B 라이브러리 둘다 해당 파일을 사용해서 서블릿 컨테이너 초기화를 시도한다. 둘다 해당 파일을 jar 안에 포함한다.
- Fat Jar를 만들면 파일명이 같으므로 A, B 라이브러리가 둘다 가지고 있는 파일 중에 하나의 파일만 선택된다. 결과적으로 나머지 하나는 포함되지 않으므로 정상 동작하지 않는다
'스프링 부트(핵심 원리와 활용)' 카테고리의 다른 글
| Ch02. 스프링 부트와 내장 톰캣 - 스프링 부트와 웹 서버 (0) | 2023.03.05 |
|---|---|
| Ch02. 스프링 부트와 내장 톰캣 - 편리한 부트 클래스 만들기 (0) | 2023.03.05 |
| Ch02. 스프링 부트와 내장 톰캣 - WAR 배포 방식의 단점 (0) | 2023.03.03 |
| Ch01. 웹 서버와 서블릿 컨테이너 - 스프링 MVC 서블릿 컨테이너 초기화 지원 (0) | 2023.03.03 |
| Ch01. 웹 서버와 서블릿 컨테이너 - 스프링 컨테이너 등록 (0) | 2023.03.03 |