Spring

Spring Boot 애플리케이션 실행 흐름과 Bean 등록 원리에 대하여

Adrian_Kim 2025. 5. 24. 23:53
Spring Boot는 간편한 설정과 강력한 자동화 기능 덕분에 많은 개발자들에게 사랑받고 있습니다.
하지만 막상 내부 구조를 깊이 들여다보면, 매우 정교하고 세부적인 단계들을 거치고 있음을 알 수 있습니다.
이 번 글에서는 SpringBoot가 애플리케이션 실행 시 내부적으로 어떤 흐름으로 동작되는지, 그리고 Bean이 어떤 기준으로 등록되는지에 대해 정리해 보고자 합니다.

 

이번 글에서 다루는 전체적인 목차는 다음과 같습니다.

  1. Spring Boot의 시작점과 전체 흐름
  2. ComponentScan의 원리와 범위
  3. Bean 후보로 등록되는 주요 애노테이션
  4. Bean 등록의 조건과 우선순위
  5. AutoConfiguration의 작동 원리

Spring Boot 애플리케이션의 시작점

대부분의 Spring Boot 애플리케이션은 아래의 코드에서 시작됩니다.

package com.example.hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class HelloApplication {

    public static void main(String[] args) {
        SpringApplication.run(HelloApplication.class, args);
    }
}

SpringApplication.run() 메서드는 다음과 같은 과정을 거칩니다.

  • SpringApplication 객체 생성 및 애플리케이션 유형 결정
    • SERVLET, REACTIVE, NONE 중 하나로 결정
  • Environment 설정 로딩 
    • application.yml 혹은 application.properties, 환경 변수 등
  • 적절한 ApplicationContext 생성
  • ComponentScan을 통한 Bean 후보 탐색
  • AutoConfiguration을 로딩 및 조건을 평가하여 Bean 등록
  • Singleton Bean들을 생성 및 의존성 주입
  • 웹 서버 (Tomcat, Jetty등) 구동
  • ApplicationReadyEvent를 발행

ComponentScan의 범위와 기준

@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 {

@SpringBootApplication은 내부적으로 @ComponentScan을 포함합니다.

ComponentScan은 Bean 후보들을 찾아내는 역할을 합니다. 기본적으로는 메인 클래스가 위치한 패키지와 그 하위 패키지 전체를 스캔합니다.

 

Bean 후보로 등록되는 주요 애노테이션

  • @Component : 기본 Bean 등록 대상
  • @Service : 서비스 레이어를 나타냄
  • @Repository : 데이터 접근 계층을 나타내며, 예외 변환 기능을 제공
  • @Controller 및 @RestController : 웹 요청 처리 Bean
  • @Configuration : 설정 클래스임을 나타냄

Bean 등록의 조건과 우선 순위

Bean 충돌 해결법
동일한 타입의 빈이 두 개 이상이면 NoUniqueBeanDefinitionException이 발생할 수 있습니다.
이를 해결하기 위해서 @Primary 혹은 @Qualifier를 사용할 수 있습니다.
interface PaymentService { }

@Service 
@Primary
public class PrimaryPaymentService implements PaymentService {
	// ... 
}

@Service("paypalService")
public class PaypalService implements PaymentService {
	// ... 
}

public class OrderService {

	private final PaymentService paymentService;
	
    public OrderService (
    	@Qualifier("paypalService") PaymentService paymentService
    ) {
    	this.paymentService = paymentService;
    }
    
    // ... 
}
  • @Primary : 인터페이스를 주입받을 때 기본적으로 받을 사용하고자 하는 Bean에 적용합니다.
  • @Qualifier : 특정 빈을 사용하고자 할 때, Bean의 이름을 기입하여 사용합니다.

AutoConfiguration 작동 원리

@SpringBootApplication 애노테이션에 @EnableAutoConfiguration이 붙어있습니다.

해당 애노테이션을 살펴보면 다음과 같습니다.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}

@Import 애노테이션의 경우 Component로 등록하고자 하는 클래스들을 명시할 수 있습니다.

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
}

AutoConfigurationImportSelector의 경우 DeferredImportSelector를 구현하고 있습니다.

DeferredImportSelector의 경우 META-INF/spring/에 있는. imports에 명시된 클래스 기준으로 Component를 등록해 주는 역할을 합니다.

SpringBoot 2.7부터 spring.factories 대신 META-INF/spring/**. imports 파일을 통해 자동 구성을 로딩합니다.

 

마무리하며

이번 글에서는 Spring Boot의 전반적인 실행 흐름과 Bean 등록 과정을 자세히 살펴보았습니다.

Spring Boot의 내부 구조를 명확히 이해하는 것은 실무에서 발생할 수 있는 Bean 충돌 문제를 효과적으로 해결하거나, 

자동 구성의 불필요한 빈 등록을 방지해 성능을 최적화하는 데 반드시 필요한 지식입니다.

이 글을 통해 스프링 프레임워크에 대한 내부 원리를 한층 더 깊이 이해하는 데 도움을 얻으셨기를 바라며 글을 마칩니다.

'Spring' 카테고리의 다른 글

[Spring] 테스트 환경 통합  (0) 2024.03.09
[Spring] @Retryable, Event 적용기  (1) 2023.12.04
Spring, 그리고 Test  (4) 2023.10.11
Spring MVC - 요청과 관련된 사용법 정리  (0) 2023.07.06
Junit에서 TestInstance의 생명주기  (0) 2023.07.03