Spring Boot

나만의 Spring Boot로 Spring Boot 원리 파악하기

작은별._. 2023. 12. 16. 14:42
728x90

이번 포스팅에서는 내장 톰캣 실행, 스프링 컨테이너 생성, 디스패처 서블릿 등록의 모든 과정을 편리하게 처리해 주는 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를 요청하면 아래와 같은 결과를 얻을 수 있습니다.


 

Postman
console

 

request 요청 후 console
전체 console

 


일반 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가지로 정리할 수 있습니다.

  1. Spring Container 생성 (ServletWebServerApplicationContextFactory.classcreateContext() 메서드)
  2. WAS (내장 톰캣) 생성 (TomcatServletWebServerFactory.classgetWebServer() 메서드)

 

코드의 구성을 각 클래스에 들어가서 살펴보면, 앞서 나만의 Spring Boot를 생성할 때 진행했던 것과 동일한 방식으로 Spring Container를 만들고 내장 톰캣을 생성하여 둘을 연결하는 과정을 진행하는 것을 확인할 수 있습니다.

 

 

이렇게 나만의 Spring Boot를 작성해 보면서 Spring Boot가 실제로 어떻게 동작하고 구성되어 있는지를 확인할 수 있었습니다. 다음 포스팅은 Spring Boot가 어떻게 Jar 파일을 편리하게 빌드하고 배포할 수 있도록 해주는지에 대해서 작성해 보겠습니다. 

 

감사합니다!


[참고자료]  

김영한, " 스프링 부트 - 핵심 원리와 활용", 인프런

728x90
반응형