SpringBoot 내장 톰캣

김신영·2023년 9월 28일
0

SpringBoot

목록 보기
2/8
post-thumbnail

WAR 배포 방식의 단점

  • Tomcat 같은 WAS를 별도로 설치해야 한다.
  • Application 코드를 WAR로 빌드해야 한다.
  • 빌드한 WAR 파일을 WAS에 배포해야 한다.

tomcat-embed.png

내장 톰캣

  • org.apache.tomcat.embed:tomcat-embed-core
    • 톰캣 내장 라이브러리

gradle 설정

dependencies {
    //스프링 MVC 추가
    implementation 'org.springframework:spring-webmvc:6.0.9'

    //내장 톰켓 추가
    implementation 'org.apache.tomcat.embed:tomcat-embed-core:10.1.6'
}

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

//Fat Jar 생성
tasks.register('buildFatJar', Jar) {
    manifest {
        attributes 'Main-Class': 'hello.embed.EmbedTomcatSpringMain'
    }
    duplicatesStrategy = DuplicatesStrategy.WARN
    from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
    with jar
}

내장 톰캣 실행

public class EmbedTomcatServletMain {

    public static void main(String[] args) throws LifecycleException {
        log.info("EmbedTomcatServletMain.main");

        // Tomcat 설정
        Tomcat tomcat = new Tomcat();

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

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

내장 톰캣 Spring 실행

public class EmbedTomcatSpringMain {

    public static void main(String[] args) throws LifecycleException {
        log.info("EmbedTomcatSpringMain.main");

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

        // Spring Container 생성
        AnnotationConfigWebApplicationContext applicationContext
            = new AnnotationConfigWebApplicationContext();
        applicationContext.register(HelloConfig.class);

        // DispatcherServlet 생성, Spring Container 연결
        DispatcherServlet dispatcherServlet = new DispatcherServlet(applicationContext);

        // Servlet 등록
        Context context = tomcat.addContext("", "/");
        tomcat.addServlet("", "dispatcher", dispatcherServlet);
        context.addServletMappingDecoded("/", "dispatcher");

        tomcat.start();
    }
}

AnnotationConfigWebApplicationContext

  • WebApplicationContext
public interface WebApplicationContext extends ApplicationContext {

	String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";

	String SCOPE_REQUEST = "request";

	String SCOPE_SESSION = "session";

	String SCOPE_APPLICATION = "application";

	String SERVLET_CONTEXT_BEAN_NAME = "servletContext";

	String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";

	String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";

	@Nullable
	ServletContext getServletContext();
}

AnnotationConfigWebApplicationContext.png

FatJar

  • ./gradlew buildJar 로 빌드할 경우
    • spring-mvc, tomcat-embed 라이브러리가 없어서 실행이 안된다.
  • FatJar의 경우, 라이브러리 jar 파일을 풀어서 class 파일을 포함시킨다.

FatJar의 단점

  • 어떤 라이브러리가 포함되어 있는지 확인하기가 어렵다.
    • 모든 라이브러리가 class로 풀려있다.
  • 파일명 중복을 해결할 수 없다.

Spring Boot 클래스 만들기

  • 내장 톰캣 실행
  • 스프링 컨테이너 생성
  • DispatcherServlet 등록

위 모든 과정을 편리하게 처리해주는 부트 클래스를 만들어 보자.

SpringApplication

public class MySpringApplication {
    
    public static void run(Class<?> configClass, String[] args) {
        // Tomcat 설정
        Tomcat tomcat = new Tomcat();
        Connector connector = new Connector();
        connector.setPort(8080);
        tomcat.setConnector(connector);

        // Spring Container 생성
        AnnotationConfigWebApplicationContext applicationContext
            = new AnnotationConfigWebApplicationContext();

        applicationContext.register(configClass);

        // DispatcherServlet 생성, Spring Container 연결
        DispatcherServlet dispatcherServlet = new DispatcherServlet(applicationContext);

        // DispatcherServlet 등록
        Context context = tomcat.addContext("", "/");
        tomcat.addServlet("", "dispatcherServlet", dispatcherServlet);
        context.addServletMappingDecoded("/", "dispatcherServlet");

        try {
            tomcat.start();
        } catch (LifecycleException e) {
            throw new RuntimeException(e);
        }
    }
}

@SpringBootApplication

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ComponentScan
public @interface MySpringBootApplication {

}

ApplicationMainClass

@MySpringBootApplication
public class MySpringBootAppMain {

    public static void main(String[] args) {
        MySpringApplication.run(MySpringBootAppMain.class, args);
    }
}

SpringBoot 와 웹 서버

  • Spring Container를 생성한다.
  • 내장 톰캣을 생성한다.
@SpringBootApplication
public class BootApplication {

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

ServletWebServerApplicationContextFactory

SpringBoot 에서 스프링 컨테이너를 생성하는 코드

class ServletWebServerApplicationContextFactory implements ApplicationContextFactory {
    // ...
    
    private ConfigurableApplicationContext createContext() {
        if (!AotDetector.useGeneratedArtifacts()) {
            return new AnnotationConfigServletWebServerApplicationContext();
        }
        return new ServletWebServerApplicationContext();
    }
}

AnnotationConfigServletWebServerApplicationContext

  • WebServerApplicationContext
public interface WebServerApplicationContext extends ApplicationContext {

	WebServer getWebServer();

	String getServerNamespace();

	static boolean hasServerNamespace(ApplicationContext context, String serverNamespace) {
		return (context instanceof WebServerApplicationContext) && ObjectUtils
				.nullSafeEquals(((WebServerApplicationContext) context).getServerNamespace(), serverNamespace);
	}

	static String getServerNamespace(ApplicationContext context) {
		return (context instanceof WebServerApplicationContext)
				? ((WebServerApplicationContext) context).getServerNamespace() : null;

	}
}

AnnotationConfigServletWebServerApplicationContext.png

TomcatServletWebServerFactory

SpringBoot 내부에서 내장 톰캣을 생성하는 코드

public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
		implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {
    @Override
    public WebServer getWebServer(ServletContextInitializer... initializers) {
        Tomcat tomcat = new Tomcat();
        // ...
        
        Connector connector = new Connector(this.protocol);
        // ...
        
        return getTomcatWebServer(tomcat);
    }
}

SpringBoot Jar 파일 구성

  • META-INF
    • MANIFEST.MF
  • BOOT-INF
    • classes : 우리가 개발한 class 파일과 resource 파일
    • lib : 외부 라이브러리
    • classpath.idx : 외부 라이브러리 경로
    • layers.idx : SpringBoot 구조 경로
  • org.springframework.boot.loader
    • JarLauncher.class : SpringBoot main() 실행 클래스

실행 가능 Jar

  • SpringBoot에서 새롭게 정의한 것
  • jar 내부에 jar를 포함할 수 있는 특별한 구조

MANIFEST.MF

Main-Class 를 제외한 나머지 필드는 Java 표준이 아니다.
스프링 부트가 임의로 사용하는 정보이다.

  • Start-Class
    • SpringBoot의 main() 실행 클래스
  • Spring-Boot-Version
    • SpringBoot 버전
  • Spring-Boot-Classes
    • 개발한 클래스 경로
  • Spring-Boot-Lib
    • 외부 라이브러리 경로
  • Spring-Boot-Classpath-Index
    • 외부 라이브러리 목록
  • Spring-Boot-Layers-Index
    • SpringBoot 구조 정보
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

classpath.idx

- "BOOT-INF/lib/lombok-1.18.28.jar"
- "BOOT-INF/lib/spring-webmvc-6.0.4.jar"
- "BOOT-INF/lib/spring-web-6.0.4.jar"
...

layers.idx

- "dependencies":
  - "BOOT-INF/lib/"
- "spring-boot-loader":
  - "org/"
- "snapshot-dependencies":
- "application":
  - "BOOT-INF/classes/"
  - "BOOT-INF/classpath.idx"
  - "BOOT-INF/layers.idx"
  - "META-INF/"
profile
Hello velog!

0개의 댓글