ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Boot 분석(구동 원리)
    기록해야 기억한다/Spring 2020. 11. 14. 00:51
    SMALL

    Spring Application 실행

    아래의 모든 설명은 spring-boot 2.3.5 기준으로 설명합니다.
    얼마전(2020/11월기준) GA 된 2.4 는 변경사항이 있습니다.

     

    아래의 코드를 실행하면 Spring Application 이 수행된다. 나머지 정보들은 전혀 노출되지 않는데 어떻게 그 많은 정보들을 처리하게 되는지에 대한 내용을 정리한 글이다.

    package com.example;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class SampleApplication {
    
    	public static void main(String[] args) {
    		SpringApplication.run(SampleApplication.class, args);
    	}
    }
    

     

    @SpringBootApplication 이 Key 인데 이는 아래와 같이 정의되어 있고 클래스 경로 설정 및 기타 Bean 및 다양한 속성 설정을 기반으로 Bean 추가를 시작하도록 하는 @EnableAutoConfiguration, 클래스가 Spring Boot Application @Configuration 을 제공함을 나타내는 @SpringBootConfiguration, @ComponentScan 같은 설정을 통해 Application 을 구성한다.

    @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 의 기본 Bean 들(SampleApplication, simpleBookRepository 은 사용자 Bean)


    기본 수행 항목

    Spring Boot 는 SpringApplication 클래스를 통해 기본적으로 많은 구성을 하고 Spring application 을 실행시킵니다. 크게 이는 다음을 수행합니다.

    • 적절한 ApplicationContext 인스턴스를 생성

    • CommandLinePropertySource 를 등록하여 argument 를 Spring 의 속성값으로 등록

    • 모든 singleton Bean 을 로딩하고 ApplicationContext refresh

    • 모든 CommandLineRunner Bean 을 트리거

    이 때 코드안에 임의로 설정들을 선언하지 않고 spring-boot.jar 패키지안에 있는 META-INF/spring.factories 파일을 기반으로 구성됩니다. main 함수를 통해 실행되는 run()에서의 흐름의 순서는 아래와 같습니다. 각 흐름에 spring.factories 파일이 관여하여 인스턴스 생성을 위한 클래스명을 제공합니다.


    SpringApplication.run() 의 상세 흐름

    1. java.awt.headless 환경변수 설정 - GUI 클래스 사용에 대한 옵션(기본 true)

      - GUI 가 필요없으면 -Djava.awt.headless=false 로 구성

    SYSTEM_PROPERTY_JAVA_AWT_HEADLESS 설정

    2. SpringApplicationRunListeners 구성

      - spring.factories 파일 기반으로 클래스들을 읽어서 Instance 로 등록하고 시작

      - apache common logger 등록

    spring.factories 파일의 RunListener 선언 부분
    Run Listeners 를 통해 EventPublishingRunListener 를 등록

    3. CommanLine 을 통해 입력된 arguments 구성하고 Listener 와 더불어 구성 환경 및 Banner 준비

      - active, default 의 프로파일을 구성하고 속성값 해석을 위한 준비 작업 수행

    기본적인 prepareEnvironment 부분 - 환경구성을 위한 준비작업을 진행한다.
    application.yml 파일을 통해 local profiles 을 지정하고 prepareEnvironment() debug 를 통해 확인할 수 있다.

     

    4. 위 환경 기반 ApplicationContext 를 생성하며 WebApplicationType 값으로 지정된 형식으로 context 를 구성하며 각 Type 에 따라 구성된 class 들을 인스턴스화 하고, 해당 context 에서 처리될 AnnotatedBean Reader/Scanner 등록,  BeanFactory 구성등을 수행. 아래의 3가지 환경에 대한 Context 를 제공

     

      - JSR 340 기반 Servlet 환경(org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext)

      - Reactive Web WebFlux 환경(org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext)

      - Web 이 아닌 일반 application 환경(org.springframework.context.annotation.AnnotationConfigApplicationContext)

    화면은 NONE 모드로 했을 경우의 debug 화면, Evaluate 창은 Servlet 클래스의 ApplicationContext 표현이다.

     

    5. 실행시 Spring Boot 의 구동 예외 현황에 대한 리포트를 위한 SpringBootExceptionReporter 구성

    spring.factoires 의 Error Reporters 선언 부분

     

    6. Context 에 환경변수를 설정하고 BeanFactory 를 통한 Singleton Bean 등록, ResourceLoader 설정, lazy 초기화구성 등의 초기화과정 수행하고 ApplicationContext 를 Refresh

     

    7. log 시작 및 listeners 시작, CommandLineRunner 들을 호출하여 실행


    spring.main.web-application-type

    앞서 언급한 것처럼 ApplicationContext 구성에 WebApplicationType 이 사용된다.

    enum 으로 선언되어 있는 WebApplicationType.java 일부분

     

    해당 enum 을 통해 Application 의 형식을 지정할 수 있으며 이를 지정하여 사용하기 위해서는 application.yml(혹은 .properties) 를 통해 아래와 같이 선언하여 사용할 수 있다.

    WebMVC 를 위해 servlet 형식으로 지정한 .yml 부분

     

    servlet 또는 reactive 를 사용하려면 그에 맞는 의존성을 추가하여야 하며 추가하지 않고 사용하면 당연하게도 아래와 같은 에러를 확인하게 된다.

    servlet 형식으로 지정했지만 의존성이 추가되지 않은 경우

     

    maven 혹은 gradle 등의 의존성 관리도구를 사용하고 있다면 아래와 같이 의존성을 추가한다.

    # gradle 을 통해 dependencies 선언
    dependencies {
    	implementation 'org.springframework.boot:spring-boot-starter-web:2.3.5.RELEASE'
    }

     

    그 외에 WebApplicationType 을 NONE 으로 지정한 경우 일반 Java Application 처럼 main thread 외에 별도로 작업을 위해 지속되는 thread 가 존재하지 않으면 바로 Application 은 종료된다.


    Spring 은 어떻게 @(annotation) 들을 찾는가?

    초기 spring 은 xml 을 통해 관련 정보들을 설정하고 해당 xml 을 application 에서 load 하여 실행하는 방식이였다. 개발 규모가 커지면서 이는 XML 파일들을 관리하기하는 또 하나의 요구사항이 생기게 되었고 이를 Annotation 사용으로 어느 정도 해결(이 또한 손이 가는건 어쩔 수 없다..)하였다.

     

    이 역시 다른 환경설정들과 마찬가지로 SpringApplication.prepareContext() 에서 구현된 load() 메서드를 통해 Bean 등을 처리한다.

    1. BeanDefinitionLoader 생성

      • AnnotatedBeanDefinitionReader 정의

      • XmlBeanDefinitionReader 정의

      • GroovyBeanDefinitionReader 정의

      • ClassPathBeanDefinitionScanner 정의 - ExcludeFilter 포함

    2. 위 단계에서 정의된 Reader/Scanner 에 BeanNameGenerator 를 통해 scan 된 각 Bean 의 이름을 구성하고 load

    위 단계에서 지정된 BeanDefinitionLoader 는 Spring 구동을 위한 Annotation (or XML 등)의 Type 들을 Set 에 담고 있고 이를 통해 관련 Annotation 들을 Scan 하여 인스턴스(내부소스에서 BeanUtils.instantiateClass()를 사용한다.) 로 만든다. 이렇게 생성된 인스턴스정보들은 아래와 같다.

    BeanDefinitionLoader 에 생성된 SpringBootApplication 의 AnnotationTypes Set
    Loader 의 includeFilters 리스트를 통해 스캔대상으로 생성된 Component, javax Bean

     

    위의 정보처럼 includeFilters 를 통해 기본적으로 @Component 와 @ManagedBean 을 Scan 할 수 있게되고 @ManagedBean 은 JEE, EJB 의 자원 주입, 라이프사이클 콜백 및 인터셉터와 같은 small set 서비스를 지원하는 인터페이스이다.

     

    이런 과정에 따라 SpringApplication 은 @Component 로 지정된 Class 를 찾게 되는데 잘 알고 있는 것처럼 @Component 로 지정되어 있는 @Configuration, @Service, @Controller, @Repository 로 지정된 Class 들을 찾아 관리 대상으로 등록한다.

     


    Application 시작할 때의 로그

    실행과정에 따른 결과로 Console 에서 해당 로그들을 확인할 수 있는데 대략 나누면 아래와 같다.

     

    Application 시작 시의 trace 로그

    위의 이미지를 보면 알겠지만 PropertySource 에서 Spring Boot 에 미리 정의된 key 들을 찾는다. 이 key 들은 Spring 의 docs(https://docs.spring.io/spring-boot/docs/2.3.5.RELEASE/reference/html/appendix-application-properties.html) 를 통해 확인할 수 있고 관련 속성값들은 application.yml 또는 application.properties 를 통해 재정의할 수 있다.

     

     

    application 파일(yml, yaml, properties) 을 로드하는 로그

    spring 의 선언대로 Config 파일들을 location 별로 찾고 발견된 config 파일을 로드한다. 각 location 의 순서는 docs.spring.io/spring-boot/docs/2.3.5.RELEASE/reference/html/spring-boot-features.html#boot-features-external-config 의 선언에 따라 수행된다.

     

    이처럼 log 를 잘 확인하면 SpringApplication 의 실행과정에 대한 상세정보를 확인할 수 있고 ConditionEvaluationReportLoggingListener 를 통해 실행과정에서의 정보(Positive matches, Negative matches, Exclusions, Unconditional classes) 를 report 받을 수 있다.

    반응형
    LIST

    댓글

Designed by Tistory.