Spring/[인프런 김영한 스프링 부트 - 핵심 원리와 활용]

[인프런 김영한 스프링 부트 - 핵심 원리와 활용] 스프링 부트와 내장 톰캣

h2boom 2024. 12. 30. 18:00

내장 톰캣

WAR 배포 방식의 단점

  • WAR 배포 방식의 단점
    • WAS를 별도로 설치해야 한다.
    • 애플리케이션 코드를 WAR로 빌드해야 한다.
    • 빌드한 WAR 파일을 WAS에 배포해야 한다.
    • 개발 환경 설정이 복잡하다.

 

  • 이 문제를 해결하기 위해 외장 서버를 라이브러리를 통해 내장 서버로 제공한다.

내장 톰캣 - 서블릿

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 지정 (예제와 같이 적용하면 된다. 중요X)
      • tomcat.addServlet() : 서블릿을 등록
      • context.addServletMappingDecoded() : 등록한 서블릿의 경로를 매핑한다. 
    • 톰캣 시작
      • tomcat.start() : 톰캣을 시작한다.
  • 내장 톰캣을 개발자가 직접 다룰일은 거의 없다. (동작 원리만 대략 파악할 것)
    • 스프링 부트에서 내장 톰캣 관련 부분을 자동화해서 제공하기 때문

내장 톰캣 - 스프링

public class EmbedTomcatSpringMain {
    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);

        //스프링 컨테이너 생성
        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();
    }
}
  • 내장 톰캣과 스프링 사용 예제
    • 내장 톰캣 설정
    • 스프링 컨테이너 생성 후 필요한 빈들을 등록
    • 스프링 MVC 디스패처 서블릿 생성 후 스프링 컨테이너와 연결
    • 디스패처 서블릿을 내장 톰캣에 등록
  • 서블릿 컨테이너 초기화와 코드가 유사하지만 시작점이 main() 메소드를 직접 실행하는지, 서블릿 컨테이너가 제공하는 초기화 메소드를 실행하는지의 차이가 있다.

내장 톰캣 - 빌드와 배포

  • 내장 톰캣 사용 시 main() 메소드를 실행하기 위해서는 jar 형식으로 빌드해야 한다.
    • jar 안에는 META-INF/MANIFEST.MF 파일에 실행할 main() 메소드의 클래스를 지정해줘야 한다.

 

Manifest-Version: 1.0
Main-Class: hello.embed.EmbedTomcatSpringMain
  • META-INF/MANIFEST.MF 파일 예제
    • Gradle의 도움을 받으면 이 과정을 쉽게 진행할 수 있다.

 

task buildJar(type: Jar) {
	manifest {
		attributes 'Main-Class': 'hello.embed.EmbedTomcatSpringMain'
	}
	with jar
}
  • Gradle에 Jar 빌드를 위한 설정

 

  • Jar 빌드: 프로젝트 폴더에서 명령어 => gradlew clean buildJar
  • jar 파일 실행 : 명령어 => java -jar jar파일명
    • 예제에서 jar 파일 실행 시 오류가 발생하는데 스프링 관련 라이브러리, 내장 톰캣 라이브러리를 찾을 수 없어서 오류가 발생

 

  • JAR 파일은 JAR 파일을 포함할 수 없다.
    • JAR 스펙의 한계로 JAR 파일 내부에는 라이브러리 역할을 하는 JAR 파일을 포함할 수 없다.
    • JAR 대신 WAR를 사용할 수도 없다.
      • WAR는 main() 메소드에서 실행할 수 있는 것이 아닌 WAS 위에서만 실행할 수 있기 때문

 

  • FatJar : JAR 안에 JAR를 포함하지 못하는 한계를 해결하기 위한 방식으로 JAR를 클래스로 대신한다.
    • JAR안에 JAR는 포함할 수 없지만 클래스는 얼마든지 포함할 수 있기에 JAR를 풀어서 나온 클래스들을 뽑아서 새로운 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
}
  • Gradle Fat Jar 생성을 위한 설정

 

  • Fat Jar 빌드: 프로젝트 폴더에서 명령어 => gradlew clean buildFatJar

 

  • Fat Jar 단점
    • 파일명 중복을 해결할 수 없다.
      • 클래스나 리소스 명이 같은 경우 하나를 포기해야 한다.
      • ex) 하나의 파일이 여러 라이브러리에 있는 경우 충돌이 발생하고 하나의 파일만 선택되고 결과적으로 정상 동작하지 않는다.
    • 모두 class로 풀기에 어떤 라이브러라기 포함되어 있는지 확인하기 어렵다.

 

스프링 부트와 웹 서버

  • 스프링 부트를 사용하면 이전 예제들의 문제를 해결해준다.
    • 내장 톰캣을 사용해서 빌드와 배포를 편리하게 한다.
    • 빌드 시 하나의 JAR를 사용하면서 동시에 Fat Jar 문제도 해결한다.
    • 내장 톰켓 서버를 실행하기 위한 복잡한 과정을 자동으로 처리한다.

 

스프링 부트와 웹 서버 - 실행 과정

@SpringBootApplication
public class BootApplication {
	public static void main(String[] args) {
		SpringApplication.run(BootApplication.class, args);
	}

}
  • 스프링 부트 main() 메소드
    • 스프링 부트 실행 시 자바 main() 메소드에서 SpringApplication.run()을 호출해주면 된다.
      • 메인 설정 정보를 인수로 넘겨줘야 하는데 @SpringBootApplication 어노테이션이 있는 현재 클래스 정보를 넘겨주면 된다.
      • run() 호출 시 스프링 컨테이너가 생성되고 WAS(내장 톰캣)를 생성한다.
    • @SpringBootApplication 어노테이션에는 컴포넌트 스캔을 포함한 여러 기능이 설정되어 있다.
      • 기본 설정은 @SpringBootApplication 어노테이션이 사용된 클래스의 현재 패키지와 그 하위 패키지를 모두 컴포넌트 스캔한다.

스프링 부트와 웹 서버 - 빌드와 배포

  • 스프링 부트 JAR 파일 분석
    • boot-0.0.1-SNAPSHOT.jar
      • META-INF
        • MANIFEST.MF
      • org/springframework/boot/loader
        • JarLauncher.class : 스프링 부트 main() 실행 클래스
      • BOOT-INF
        • classes : 우리가 개발한 class 파일과 리소스 파일
          • hello/boot/BootApplication.classhello/boot/controller/HelloController.class
        • lib : 외부 라이브러리
          • spring-webmvc-6.0.4.jar
          • tomcat-embed-core-10.1.5.jar 등등...
        • classpath.idx : 외부 라이브러리 경로
        • layers.idx : 스프링 부트 구조 경로
  • 스프링 부트 JAR는 Fat Jar 구조가 아닌 새로운 구조로 만들어져 있으며 JAR 내부에 JAR도 포함되어 있다.
  • 빌드 시 생성되는 ~plain.jar 파일은 우리가 개발한 코드만 순수한 jar로 빌드한 것 (외부 라이브러리 포함X)

스프링 부트 실행 가능 Jar

  • 실행 가능 Jar : Fat Jar의 문제를 해결하기 위한 스프링 부트의  실행 가능 Jar
    • Jar 내부에 Jar를 포함할 수 있는 특별한 구조다.
      • 어떤 라이브러리가 포함되어있는지 식별하기 쉽다.
      • class가 아닌 Jar를 포함하기 때문에 파일명 중복도 상관없다.

 

Manifest-Version: 1.0
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: hello.boot.BootApplication
Spring-Boot-Version: 3.0.2
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Build-Jdk-Spec: 17
  • META-INF/MANIFEST.MF 파일
    • jar 실행 시 동작 과정
      1. java -jar jar파일명을 실행
      2. META-INF/MANIFEST.MF 파일을 찾아서 인식한다.
        • Main-Class를 읽어서 main() 메소드를 실행한다.
      3. Main-Class에는 main()이 있는 BootApplication이 아닌 JarLauncher라는 클래스가 적혀있고 JarLauncher의 main() 메소드를 실행한다.
        • BOOT-INF/classes/ 인식
        • BOOT-INF/lib/ 인식
      4. JarLauncher가 작업을 처리 후 Start-Class에 적힌 BootApplication main()을 호출해준다.

정리

  • 내장 톰캣 사용 시 main() 메소드를 실행하기 위해서는 jar 형식으로 빌드해야 한다.
    • jar 안에는 META-INF/MANIFEST.MF 파일에 실행할 main() 메소드의 클래스를 지정해줘야 한다.

 

  • 기본적으로 JAR 파일은 내부에 JAR 파일을 포함할 수 없다.
    • Far Jar는 Jar 내부의 class들을 뽑아서 새로운 Jar로 만든다. (파일명 중복, 라이브러리 식별 문제점이 있다.)

 

  • 스프링 부트는 내장 WAS(톰캣)을 가지고 있다.

 

  • 스프링 부트 main() 메소드
    • 스프링 부트 실행 시 자바 main() 메소드에서 SpringApplication.run()을 호출해주면 된다.
      • 메인 설정 정보를 인수로 넘겨줘야 하는데 @SpringBootApplication 어노테이션이 있는 현재 클래스 정보를 넘겨주면 된다.
      • run() 호출 시 스프링 컨테이너가 생성되고 WAS(내장 톰캣)를 생성한다.
    • @SpringBootApplication 어노테이션에는 컴포넌트 스캔을 포함한 여러 기능이 설정되어 있다.
      • 기본 설정은 @SpringBootApplication 어노테이션이 사용된 클래스의 현재 패키지와 그 하위 패키지를 모두 컴포넌트 스캔한다.

 

  • 스프링 부트 JAR는 Fat Jar 구조가 아닌 새로운 구조(= 실행 가능 Jar)로 만들어져 있으며 JAR 내부에 JAR도 포함되어 있다.
    • 스프링 부트 JAR 실행 시 동작 흐름
      • Main-Class에는 BootApplication이 아닌 JarLauncher라는 클래스가 적혀있고 JarLauncher의 main() 메소드를 실행한다.
      • JarLauncher가 작업을 처리 후 Start-Class에 적힌 BootApplication main()을 호출해준다.

출처 : [인프런 김영한 스프링 부트 - 핵심 원리와 활용]

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-%ED%99%9C%EC%9A%A9/dashboard

 

스프링 부트 - 핵심 원리와 활용 강의 | 김영한 - 인프런

김영한 | 실무에 필요한 스프링 부트는 이 강의 하나로 모두 정리해드립니다., 백엔드 개발자를 위한 스프링 부트 끝판왕! 실무에 필요한 내용을 모두 담았습니다.  [임베딩 영상] 김영한의 스

www.inflearn.com