Spring

Springboot 동작 방식 및 분석하는 방법

꾸준함. 2023. 3. 15. 18:03

개요

Spring Initialzr가 제공해 주는 프로젝트 템플릿 덕분에 개발자들은 손쉽게 스프링 부트 프로젝트 개발을 시작할 수 있습니다.

버튼 몇 번 클릭만 하면 개발을 바로 진행할 수 있기 때문에 편리하다는 장점이 있지만, 이로 인해 저를 포함해서 많은 개발자들이 스프링 부트의 내부 동작 과정 및 AutoConfiguration에 의해 추가된 빈들을 배우지 않는 단점이 있습니다.

따라서, 이번 게시글에서는 스프링 부트가 내부적으로 구성 정보를 어떻게 추가하는지 살펴보고 추가된 bean들에 대해 확인하는 방법을 간략하게 소개하겠습니다.

글을 읽기에 앞서 아래 게시글들을 참고하시면 이해가 더 잘 될 것 같습니다.

https://jaimemin.tistory.com/2259

 

[Spring Boot] AutoConfiguration

개요 스프링 부트의 Bean 구성 정보는 Component Scan에 의해서 등록되는 Bean과 AutoConfiguration에 의해 자동으로 등록되는 빈으로 구분됩니다. 이번 게시글에서는 일반적으로 자동으로 등록되는 빈과

jaimemin.tistory.com

https://jaimemin.tistory.com/2260

 

[Spring] @Conditional 정리

개요 스프링 4.0부터 추가된 Conditional 애노테이션에 대해 간단히 정리해 보겠습니다. @Conditional & Condition 인터페이스 앞서 언급했듯이 @Conditional은 Spring 4.0부터 추가가 되었고 이름 뜻대로 모든 조

jaimemin.tistory.com

 

Springboot가 Bean을 등록하는 과정

 

스프링 부트는 크게 두 가지 트랙을 통해 빈을 등록합니다.

  • Springboot가 내장한 클래스들을 통해 빈을 등록
  • 개발자가 구현한 코드를 통해 빈을 등록

 

1. Springboot가 내장한 클래스들을 통해 빈을 등록

 

  • 개발자가 기술 스택을 선정 (기술 스택, 언어, web 사용 유무 [사용한다면 servlet 기반 vs reactive], caching, 보안, etc)
  • Spring Initialzr를 통해 프로젝트 템플릿 생성
    • maven일 경우 pom.xml
    • gradle일 경우 build.gradle
  • @AutoConfiguration 애노테이션을 통해 Spring Container에 등록될 후보 bean 로딩
    • 144가지의 Core AutoConfiguration 로딩
    • Spring Actuator, Spring Cloud와 같은 스프링 프로젝트 이용 시 해당 관련 bean들이 후보로 추가
  • @Conditional 애노테이션에 의해 필터링 되어 최종 Bean이 등록
  • Spring Environment 추상화 통해 외부 다양한 properties 혹은 yml로부터 외부 설정 값 읽어옴
  • Application Infrastructure Bean 및 Container Infrastructure Bean 등록

 

2. 유저가 추가한 빈 등록

 

  • 개발자에 의해 @ComponentScan 대상으로 만들어진 클래스를 빈으로 등록
    • @Component 혹은 해당 애노테이션을 메타 애노테이션으로 갖는 애노테이션이 붙은 클래스
  • 개발자가 Spring에서 자동으로 제공하는 빈을 대체하는 Custom Infrastructure Bean을 구현했을 경우 1번에서 등록된 빈을 대체
    • 스프링이 내장한 클래스 중 @ConditionalOnMissingBean 애노테이션이 붙어있는 클래스의 경우 커스텀 빈 등록 시 등록이 안되도록 처리가 되어 있음
  • pom.xml 혹은 gradle에 추가된 써드 파티에 의해 추가된 빈 등록
  • 유저 구성 Application Bean 등록

 

위와 같이 1번과 2번 과정을 거쳐 최종적으로 스프링 부트가 우리가 개발한 하나의 애플리케이션 구성 정보를 이루게 됩니다.

 

AutoConfiguration에 의해 등록된 Bean을 확인하는 방법

앞서 스프링 부트가 빈을 등록하는 과정을 학습했으니 이제 실제로 어떤 빈들이 스프링 컨테이너에 등록이 되었는지 확인하는 방법을 알아보겠습니다.

후보 빈들까지 확인하면 너무 양이 많아지기 때문에 실제 등록된 빈들만 확인하는 과정만 알아볼 것이며 이는 스프링에 의해 자동으로 등록되는 ConditionEvaluationReport 빈을 주입받아 확인할 수 있습니다.

@SpringBootApplication 애노테이션이 붙어있는 클래스에 아래 메서드를 추가하시면 등록된 빈과 등록된 사유를 간략하게 알 수 있습니다. (자바에서 제공하는 jmx 빈이 많이 등록되기 때문에 jmx 빈은 출력되지 않도록 처리했습니다.)

 

 

위와 같이 AutoConfiguration에 의해 추가된 빈들을 확인하고 아래에 나열할 자료들을 참고하면서 어떤 조건으로 동작할지 분석하면 스프링 부트 동작 과정을 완벽하게 이해할 수 있을 것입니다.

  • 스프링 부트 공식 문서
  • 추가된 빈들의 소스 코드 및 Property 클래스와 Customizer 등등
    • Property 클래스
      • ex) ServerProperties.class
    • Customizer
      • ApplicationContext의 생성 및 초기화 단계에서 사용자 정의 설정을 제공하는 인터페이스
      • ApplicationContext가 생성될 때 Customizer를 통해 스프링 빈을 추가하거나 빈의 속성을 변경 가능
      • ex) PlatformTransactionManagerCustomizer

 

분석하는 방법 예시

spring-boot-starter를 dependency로 등록했을 때 추가되는 bean을 분석하는 방법으로 간단한 예시를 들어보겠습니다.

앞서 AopAutoConfiguration 클래스가 리스트 최상단에 있으므로 AopAutoConfiguration 클래스 소스코드를 확인해보겠습니다.

 

 

 

해당 클래스에는 @ConditionalOnProperty 애노테이션이 붙어있는데 spring.aop.* 설정 정보를 properties 혹은 yml에 추가하지 않았는데도 불구하고 등록이 되는 것을 확인할 수 있습니다.

이는 마지막 속성인 matchIfMissing = true에 의해 추가가 된 것인데 해당 속성이 참일 경우 spring.aop.* 프로퍼티가 존재하지 않더라도 조건이 매칭되기 때문에 추가가 됩니다.

 

TaskExecutionAutoConfiguration도 spring-boot-starter를 dependency로 등록했을 때 자동으로 추가되는 빈인데 해당 소스 코드는 아래와 같습니다.

 

 

 

소스 코드를 확인해 보면 Builder 클래스를 빈으로 주입받는데 이는 Task Executor에서 사용하는 쓰레드 풀을 하나 이상으로 분리해서 사용하는 케이스가 있을 수 있기 때문입니다.

위와 같은 케이스일 경우 TaskExecutorBuilder를 주입받아서 ThreadPoolTaskExecutor 빈을 생성하는 코드를 작성하면 됩니다.

RestTemplateBuilder 또한 Builder를 빈으로 주입받는 예시이며 확인해 보면 의외로 Builder를 빈으로 주입받는 케이스가 많습니다.

(RestTemplateBuilder의 경우 빌더만 AutoConfiguration으로 제공하기 때문에 RestTemplate을 사용하고 싶은 경우 RestTemplateBuilder를 주입받아서 필요한 설정 추가를 하고 Bean으로 등록해서 사용해야 합니다.)

 

TaskExecutionAutoConfiguration 클래스에서 중요한 점은 스프링에서 제공하는 Thread Core Size는 디폴트로 8인 반면 사용자가 직접 구현할 경우 Core Size가 1이므로 필요시 직접 설정해야 한다는 점입니다.

이는 ThreadPoolTaskExecutor 클래스 내 corePoolSize가 1로 정의되어 있지만 TaskExecutionAutoConfiguration에서는 TaskExecutionProperties를 활성화시켜 8로 초기화시키기 때문입니다.

위 내용을 모르는 상태로 별도 ThreadPoolTaskExecutor 빈을 생성하면 corePoolSize가 1로 설정되어 버그가 발생할 확률이 높아집니다.

마찬가지로 다른 빈들도 스프링 내부적으로 등록했을 때와 개발자가 수동으로 등록했을 때 차이를 보이는 케이스가 있을 수 있습니다.

따라서, 스프링 부트에서 자동으로 등록한 빈들의 내부 동작 과정을 어느 정도 파악할 줄 알아야 핵심적인 개발자로 성장할 수 있다고 생각합니다.

 

참고

인프런 토비의 스프링 부트 - 이해와 원리 

 

반응형