이번 포스팅에서는 내장 톰캣 실행, 스프링 컨테이너 생성, 디스패처 서블릿 등록의 모든 과정을 편리하게 처리해 주는 Boot 클래스를 만들어보면서 어떻게 Spring Boot가 동작하는지 알아보겠습니다. 이 포스팅을 보기 전에 아래 포스팅을 먼저 참고하면 이해에 도움이 되실 겁니다!!
Spring Boot와 내장 톰캣
내장 톰캣은 WAR 방식의 단점을 보완하기 위해서 등장하였습니다. (참고: 외장 서버와 내장 서버) 내장 톰캣은 쉽게 말해 톰캣을 라이브러리로 포함하고 자바 코드로 직접 실행하는 것을 의미합
silver-programmer.tistory.com
본 포스팅에서 사용할 코드의 프로젝트 구조는 아래와 같습니다.

또한 build.gradle 파일에는 아래와 같은 dependency들이 추가되어야 합니다.
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'
}
본격적으로 코드를 작성해 보겠습니다.
MySpringApplication.class
해당 클래스에는 tomcat을 연결하여 스프링 컨테이너 생성 및 디스패처 서블릿을 생성하는 코드를 작성하였습니다.
public class MySpringApplication {
public static void run(Class configClass, String[] args) throws IOException, LifecycleException {
System.out.println("MySpringBootApplication.run args = "+ List.of(args));
// tomcat 설정
Tomcat tomcat = new Tomcat();
Connector connector = new Connector();
connector.setPort(8080); // port 지정
tomcat.setConnector(connector); // tomcat을 port 8080에 연결
// 스프링 컨테이너 생성
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(configClass); // 설정 파일을 파라미터로 전달받음
// 스프링 MVC Dispatcher Servlet 생성 + 스프링 컨테이너 연결
DispatcherServlet dispatcherServlet = new DispatcherServlet(applicationContext);
String docBase = Files.createTempDirectory("tomcat-basedir").toString();
// Dispatcher Servlet 등록
Context context = tomcat.addContext("", docBase); // tomcat에 사용할 contextPath, docBase 지정
tomcat.addServlet("", "dispatcher", dispatcherServlet); // tomcat에 Dispatcher Servlet 등록
context.addServletMappingDecoded("/", "dispatcher"); // 등록한 Dispatcher Servlet의 경로 mapping
tomcat.start(); // tomcat 시작
}
}
MySpringBootApplication.class
그 후, 아래 애너테이션을 만듭니다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ComponentScan // 컴포넌트 스캔 기능이 추가된 애노테이션
public @interface MySpringBootApplication { }
MySpringBootMain.class
해당 애너테이션을 아래처럼 작성된 MySpringBootMain 클래스에 적용합니다.
@MySpringBootApplication
public class MySpringBootMain {
public static void main(String[] args) throws LifecycleException, IOException {
System.out.println("MySpringBootMain.main");
MySpringApplication.run(MySpringBootMain.class, args);
}
}
이렇게 설정하고 MySpringBootMain 클래스를 실행시키면, @MySpringBootApplication으로 인해 Component Scan이 일어나서 아래 Controller bean을 스캔하여 등록해 줍니다. Controller는 MyController.class에 작성되어 있습니다.
@RestController
public class MyController {
@GetMapping("/hello-spring")
public String hello() {
System.out.println("MyController.hello");
return "hello spring!";
}
}
( 이전에 사용했던 MyConfig.class 안의 빈들은 모두 지워주면 됩니다.)
//@Configuration
public class MyConfig {
// @Bean
public MyController helloController() {
return new MyController();
}
}
이제 실행한 후 request를 요청하면 아래와 같은 결과를 얻을 수 있습니다.




일반 SpringBoot
실제 Spring Boot 애플리케이션도 위에서 작성한 것과 유사하게 구성되어 있습니다. Spring Boot 애플리케이션을 생성하면 아래와 같이 자동으로 main 함수가 만들어지는 것을 본 적 있을 것입니다.
@SpringBootApplication
public class BootApplication {
public static void main(String[] args) {
SpringApplication.run(BootApplication.class, args);
}
}
@SpringBootApplication 내부에는 위에서 저희가 작성한 @MySpringBootApplication과 비슷하게 구성되어 있습니다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
실제 Spring Boot를 사용하면 직접 작성했던 내장 톰캣 서버를 실행하기 위한 복잡한 코드 작성을 하지 않아도 되어 편리하게 내장 톰캣을 사용해서 빌드와 배포를 가능케 합니다.
실제로 Spring Boot를 이용해 웹 애플리케이션을 개발할 때는 아래의 의존성을 Gradle에 추가합니다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
spring-boot-starter-web 안에는 아래와 같은 dependency들을 모두 포함하고 있습니다.
- spring-boot-starter
- jackson
- spring-core
- spring-mvc
- spring-boot-starter-tomcat: 내장 톰캣 (tomcat-embed-core)
또한, Spring Boot를 이용하면 라이브러리 뒤에 버전 정보가 없습니다. 이는 Spring Boot가 현재 Spring Boot 버전에 가장 적절한 외부 라이브러리 버전을 자동으로 선택해 주기 때문입니다. (너무 편리한 것 같네요..)
Spring Boot 실행
Spring Boot 애플리케이션을 실행하면, main() 메서드의 SpringApplication.run()이 호출됩니다. 여기에 메인 설정 정보를 넘겨주는데, 보통 @SpringBootApplication 애너테이션이 있는 현재 클래스를 지정합니다.
Spring Boot 실행의 핵심은 2가지로 정리할 수 있습니다.
- Spring Container 생성 (ServletWebServerApplicationContextFactory.class의 createContext() 메서드)
- WAS (내장 톰캣) 생성 (TomcatServletWebServerFactory.class의 getWebServer() 메서드)
코드의 구성을 각 클래스에 들어가서 살펴보면, 앞서 나만의 Spring Boot를 생성할 때 진행했던 것과 동일한 방식으로 Spring Container를 만들고 내장 톰캣을 생성하여 둘을 연결하는 과정을 진행하는 것을 확인할 수 있습니다.
이렇게 나만의 Spring Boot를 작성해 보면서 Spring Boot가 실제로 어떻게 동작하고 구성되어 있는지를 확인할 수 있었습니다. 다음 포스팅은 Spring Boot가 어떻게 Jar 파일을 편리하게 빌드하고 배포할 수 있도록 해주는지에 대해서 작성해 보겠습니다.
감사합니다!
[참고자료]
김영한, " 스프링 부트 - 핵심 원리와 활용", 인프런
'Spring Boot' 카테고리의 다른 글
Spring Boot의 외부설정 (0) | 2023.12.25 |
---|---|
Jar 파일의 빌드와 배포 (0) | 2023.12.16 |
Spring Boot와 내장 톰캣 (0) | 2023.12.16 |
외장 서버 vs 내장 서버 (0) | 2023.12.16 |
@PostConstruct, @PreDestroy로 빈 생명주기 관리하기 (1) | 2023.12.06 |