WEB/Spring Boot

스프링 부트와 내장 톰캣

Tony Lim 2023. 5. 10. 19:46
728x90

WAR 배포 방식의 단점

애플리케이션 코드를 war로 빌드한 후에 별도로 WAS에 배포하는 과정을 거쳐야한다.
이 과정이 귀찮고 복잡하다.

톰캣도 어차피 자바니까 main()이 실행되는 순간 같이 부팅되게 하고싶다!(톰켓을 라이브러리처럼) == 내장 톰켓기능 탄생

public class EmbedTomcatServletMain {

    public static void main(String[] args) throws LifecycleException {
        System.out.println("EmbedTomcatServletMain.main");

        // configure tomcat
        Tomcat tomcat = new Tomcat();
        Connector connector = new Connector();
        connector.setPort(8080);
        tomcat.setConnector(connector);

        // enroll servlet
        Context context = tomcat.addContext("", "/");
        tomcat.addServlet("","helloServlet",new HelloServlet());
        context.addServletMappingDecoded("/hello-servlet","helloServlet");
        tomcat.start();
    }
}

제대로 쓰러면 디테일 한 부분의 예외처리 같은것들을 해야하지만 spring boot에서 내부적으로 다 처리해주기 때문에 굳이 알 필요는 없다.

public class EmbedTomcatSpringMain {

    public static void main(String[] args) throws LifecycleException {
        System.out.println("EmbedTomcatSpringMain.main");

        // configure tomcat
        Tomcat tomcat = new Tomcat();
        Connector connector = new Connector();
        connector.setPort(8080);
        tomcat.setConnector(connector);

        // create spring container
        AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
        appContext.register(HelloConfig.class);
        
        // create spring mvc dispatcher servlet and connect with container
        DispatcherServlet dispatcher = new DispatcherServlet(appContext);
        
        // enroll dispatcher servlet to container
        Context context = tomcat.addContext("", "/");
        tomcat.addServlet("","dispatcher",dispatcher);
        context.addServletMappingDecoded("/","dispatcher");

        tomcat.start();
    }
}

톰캣에 spring container 를 인지하고 있는 dispatcher servlet을 배포하고 부팅한다.


내장 톰캣4 - 빌드와 배포1

//일반 Jar 생성
task buildJar(type: Jar) {
    manifest {
        attributes 'Main-Class': 'hello.embed.EmbedTomcatSpringMain'
    }
    with jar
}

Main-Class로 어떤 main method를 실행할지 알려준다. 하지만 이상태에서는 내가만든 클래스들만 컴파일되어서 묶여있다. 
필요한 spring ,tomcat등이 들어가 있지 않게 된다.

과거 war의 경우와 비교하면 현재는 classes만 존재하는 경우다.

jar 파일은 다른 외부 jar를 포함할 수 없다. = 스펙이 그냥 그런것이다.


내장 톰캣5 - 빌드와 배포2

//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
}

jar를 포함할 수 는없어도 어차피 까보면 클래스이기 때문에 클래스를 포함하도록 한다.
이것을 Fat Jar이라 보통 부른다.

모두 클래스로 풀려있으니 어떤 라이브러리가 사용되고 있는지 추적하기 어려움

파일명 중복을 해결할 수 없다.
클래스나 리소스명이 같은 경우 둘중 하나를 포기해야한다.  -> 정상동작하지 않음


편리한 부트 클래스 만들기

public class MySpringApplication {

    public static void run(Class configClass , String[] args) {
        System.out.println("MySpringApplication.main args= "+ List.of(args));

        // configure tomcat
        Tomcat tomcat = new Tomcat();
        Connector connector = new Connector();
        connector.setPort(8080);
        tomcat.setConnector(connector);

        // create spring container
        AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
        appContext.register(configClass);

        // create spring mvc dispatcher servlet and connect with container
        DispatcherServlet dispatcher = new DispatcherServlet(appContext);

        // enroll dispatcher servlet to container
        Context context = tomcat.addContext("", "/");
        tomcat.addServlet("","dispatcher",dispatcher);
        context.addServletMappingDecoded("/","dispatcher");

        try {
            tomcat.start();
        } catch (LifecycleException e) {
            throw new RuntimeException(e);
        }
    }
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ComponentScan
public @interface MySpringBootApplication {
}
@MySpringBootApplication
public class MySpringBootMain {
    public static void main(String[] args) {
        System.out.println("MySpringBootMain.main");
        MySpringApplication.run(MySpringBootMain.class, args);
    }
}

main 메소드가 최상위 패키지에 존재하여 @MySpringBootApplication 을 통해 필요한 빈들을 scan한다음 dispatcher servlet에게 사용하게끔 한다.

스프링부트에서도 내장톰켓을 만들고 dispatcherServlet을 통해 spring container 물게되고 servlet으로 내장톰켓에 등록을 하게 된다.....?


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

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

빌드해서 MANIFEST.MF 를 확인해보면 main-class가 우리가 작성한 BootApplication이 아니다 JarLauncher라는 클래스인것을 알 수 있다.

또한 jar안에는 jar이 들어가지 못하는데 BOOT-INF/lib 안에 jar이 들어가 있는 것을 알 수 있다.


스프링 부트 실행 가능 Jar

jar spec에서는 META-INF/MANIFEST.MF 에 써있는 class 의 main 메소드를 실행을 하게 된다.
여기서는 JarLauncher.class의 main메소드를 실행하게 되어있다.

빌드후에 org.springframework.boot.loader.에 여러 유틸클래스가 생성되게 되고 JarLauncher등등여러 클래스에서
스프링부트의 jar안에서 jar를 읽어오는 작업을 가능하게 해준다. 


 

 

 

728x90