나만의 Spring Boot로 Spring Boot 원리 파악하기
이번 포스팅에서는 내장 톰캣 실행, 스프링 컨테이너 생성, 디스패처 서블릿 등록의 모든 과정을 편리하게 처리해 주는 Boot 클래스를 만들어보면서 어떻게 Spring Boot가 동작하는지 알아보겠습니다. 이 포스팅을 보기 전에 아래 포스팅을 먼저 참고하면 이해에 도움이 되실 겁니다!!
본 포스팅에서 사용할 코드의 프로젝트 구조는 아래와 같습니다.
또한 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 파일을 편리하게 빌드하고 배포할 수 있도록 해주는지에 대해서 작성해 보겠습니다.
감사합니다!
[참고자료]
김영한, " 스프링 부트 - 핵심 원리와 활용", 인프런