JavaScript

웹 개발의 필수 언어

동적인 웹 페이지 구현을 위한 핵심 프로그래밍 언어.

Java

객체지향 프로그래밍

안정적이고 확장성 있는 백엔드 개발의 대표 언어.

HTML

웹의 기초

웹 페이지의 구조를 정의하는 마크업 언어.

React

현대적 UI 라이브러리

효율적인 사용자 인터페이스 구축을 위한 JavaScript 라이브러리.

CSS

웹 디자인의 핵심

웹 페이지의 시각적 표현을 담당하는 스타일 언어.

Spring

자바 웹 프레임워크

기업급 애플리케이션 개발을 위한 강력한 프레임워크.

Spring

✨ 스프링 빈(Bean) 조회: 컨테이너 객체 활용 가이드

lamarcK 2025. 5. 29. 16:37

스프링에서 스프링 빈(Bean) 조회는 스프링 컨테이너(Spring Container)에 등록된 객체들을 찾아보는 방법이다. 빈 확인은 주로 테스트(Test) 상황에서 많이 활용한다. 빈 등록 방법은 크게 자동 빈 등록(Automatic Bean Registration)과 수동 빈 등록(Manual Bean Registration)으로 나뉘며, 등록 방법에 따라서 조회 방법 또한 달라진다.

🎯 주요 사용 목적

스프링 빈 조회는 다음과 같은 목적으로 사용된다.

  • 등록된 빈 확인 : 원하는 빈이 제대로 등록(Registration)되었는지 확인하고, 어떤 빈들이 등록되어 있는지 전체적으로 파악(Overall Grasp)한다.
  • 문제 해결 : 의존성 주입(Dependency Injection)이 제대로 되었는지 확인하고, 빈 등록 관련 문제 발생 시 디버깅(Debugging)을 수행한다.
  • 빈 사용 : 필요한 빈을 가져와서 사용하거나, 특정 타입의 빈들을 모두 가져와서 사용한다.

📚 빈 등록 방식

스프링 빈은 크게 두 가지 방식으로 컨테이너에 등록된다.

자동 빈 등록 (@Component 스캔)

스프링의 컴포넌트 스캔(Component Scan) 기능을 활용하여 자동으로 빈을 등록하는 방식이다. 주로 @Component 어노테이션과 이를 포함하는 @Service, @Repository, @Controller 등의 어노테이션을 사용한다.

@Component  // 자동 등록 대상
public class MemberService { }

@Service    // @Component 포함 (비즈니스 로직 담당)
public class OrderService { }

@Repository // @Component 포함 (데이터 접근 계층 담당)
public class MemberRepository { }

수동 빈 등록 (@Configuration + @Bean)

개발자가 @Configuration 클래스 내부에서 @Bean 어노테이션을 사용하여 빈을 직접 정의하고 등록하는 방식이다.

@Configuration // 스프링 설정 클래스임을 명시
public class AppConfig {

    @Bean // 빈으로 등록될 메서드
    public MemberService memberService() {
        return new MemberServiceImpl(); // 직접 객체를 생성하여 반환
    }
}

⚙️ 사전 필요 설정 : 스프링 컨테이너 생성

빈을 조회하기 위해서는 먼저 스프링 컨테이너(Spring Container)를 생성해야 한다. 스프링 빈은 스프링 컨테이너가 관리하는 객체이므로, 테스트 코드에서 스프링 컨테이너를 만들지 않으면 빈 조회가 불가능하다.

// 스프링 컨테이너 생성
// AnnotationConfigApplicationContext는 ApplicationContext의 구현체로,
// 어노테이션 기반 설정 클래스를 통해 빈을 로드한다.
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

참고 : AnnotationConfigApplicationContextApplicationContext 인터페이스의 구현체이다. ApplicationContext는 빈 조회와 같은 기본적인 컨테이너 기능을 제공하는 핵심 인터페이스(Core Interface)이며, AnnotationConfigApplicationContext는 이를 구현하여 어노테이션 기반(Annotation-based)의 설정 클래스를 사용하여 스프링 컨테이너를 초기화할 때 주로 사용된다. 빈 조회뿐만 아니라 빈 메타정보(BeanDefinition) 조회 등 더 풍부한 기능을 제공한다.

📌 빈 역할 구분

  • ApplicationContext : 단순 빈 조회만 필요할 때 주로 사용하는 인터페이스 타입(Interface Type)으로 컨테이너를 선언한다.
  • AnnotationConfigApplicationContext : 빈 메타정보(BeanDefinition) 확인 등 더 상세한 컨테이너 기능이 필요할 때 사용하는 구현체 타입(Implementation Type)으로 컨테이너를 선언한다.

🗄️ 자주 사용되는 메서드

스프링 컨테이너에서 빈을 조회할 때 다음 메서드들이 주로 사용된다.

  • getBeanDefinitionNames() : 컨테이너에 등록된 모든 빈의 이름(All Bean Names)을 조회한다.
  • getBean() : 특정 빈을 이름(Name) 또는 타입(Type)으로 조회한다.
  • getBeansOfType() : 특정 타입(Type)의 모든 빈(All Beans)을 조회한다.
  • containsBean() : 특정 빈의 존재 여부(Existence Check)를 확인한다.
  • getType() : 빈의 타입(Type)을 조회한다.
  • getBeansWithAnnotation() : 특정 어노테이션(Annotation)이 붙은 빈들을 조회한다.

🔍 빈 조회 방법

스프링 컨테이너에 등록된 빈을 조회하는 방법은 크게 단일 빈 조회(Single Bean Lookup)와 다중 빈 조회(Multiple Bean Lookup)로 나눌 수 있다.

단일 빈 조회 (Single Bean Lookup)

특정 빈 하나를 정확하게 조회할 때 사용한다.

  • 타입으로 조회 : ac.getBean(Type.class) 메서드를 사용하여 지정된 타입(Type)의 빈을 조회한다.
@Test @DisplayName("타입으로만 조회")
void findBeanByType() {
    MemberService memberService = ac.getBean(MemberService.class);
    System.out.println("memberService = " + memberService);
}

조회하고 싶은 타입을 ac.getBean(타입.class)의 타입으로 변경하면 된다. 이때 왼쪽의 변수 타입도 일치시켜야 하며, 일치시키지 않을 경우 컴파일 오류가 발생한다.

// OrderService 타입으로 조회할 경우
OrderService orderService = ac.getBean(OrderService.class);

// MemberRepository 타입으로 조회할 경우
MemberRepository memberRepository = ac.getBean(MemberRepository.class);

주의 사항 : 동일한 타입의 빈이 두 개 이상 존재하면 NoUniqueBeanDefinitionException이 발생한다. 이러한 예외 상황에 대한 해결 방법은 'VII. 빈 조회 시 발생할 수 있는 주요 예외 상황' 섹션에서 상세히 다룬다.

  • 빈 이름으로 조회 : ac.getBean("beanName", Type.class) 메서드를 사용하여 지정된 빈 이름(Bean Name)과 타입(Type)으로 빈을 조회한다.
@Test @DisplayName("빈 이름으로 조회")
void findBeanByName() {
    MemberService memberService = ac.getBean("memberService", MemberService.class);
    System.out.println("memberService = " + memberService);
}
  • 구체 타입으로 조회 : ac.getBean(ConcreteType.class) 메서드를 사용하여 특정 인터페이스가 아닌 구체 클래스(Concrete Class)의 타입으로 빈을 조회한다. 이는 특정 구현체에 의존하는 경우에 사용될 수 있다.
@Test @DisplayName("구체 타입으로 조회")
void findBeanByConcreteType() {
    MemberServiceImpl memberService = ac.getBean(MemberServiceImpl.class);
    System.out.println("memberService = " + memberService);
}

다중 빈 조회 (Multiple Bean Lookup)

특정 조건에 맞는 여러 개의 빈을 한 번에 조회할 때 사용한다.

  • 특정 타입의 모든 빈 조회 : ac.getBeansOfType(Type.class) 메서드를 사용하여 지정된 타입(Type)에 해당하는 모든 빈을 Map<String, T> 형태로 조회한다. 이 방법은 NoUniqueBeanDefinitionException을 회피하면서, 특정 인터페이스의 모든 구현체를 동적으로 처리해야 할 때 유용하다.
@Test @DisplayName("특정 타입 빈 출력")
void findAllBeanByType() {
    Map<String, MemberService> beansOfType = ac.getBeansOfType(MemberService.class);
    for (String key : beansOfType.keySet()) {
        System.out.println("key = " + key + " value = " + beansOfType.get(key));
    }
}
  • 특정 애노테이션이 붙은 빈 조회 : ac.getBeansWithAnnotation(Annotation.class) 메서드를 사용하여 특정 어노테이션이 붙은 모든 빈을 Map<String, Object> 형태로 조회한다. 이는 @Component, @Service, @Repository 등과 같이 특정 역할을 나타내는 애노테이션이 적용된 빈들을 그룹으로 관리할 때 활용된다. 자동 등록된 빈을 조회할 때 특히 유용하다.
@Test void findComponentBean() {
    // @Component 어노테이션이 붙은 모든 빈 조회
    Map<String, Object> beansWithAnnotation =
        ac.getBeansWithAnnotation(Component.class);
    // ... (필요한 추가 로직)

    // @Service 어노테이션이 붙은 빈들 조회
    Map<String, Object> services =
        ac.getBeansWithAnnotation(Service.class);
    // ...
}
  • 모든 빈 출력 (애플리케이션 빈 필터링 포함) : ac.getBeanDefinitionNames()를 사용하여 스프링 컨테이너에 등록된 모든 빈의 이름(All Bean Names)을 조회할 수 있다. 이는 주로 컨테이너에 어떤 빈들이 등록되어 있는지 전체적으로 파악(Overall Grasp)하거나 디버깅(Debugging) 목적으로 사용된다.
@Test @DisplayName("모든 빈 출력")
void findAllBean() {
    String[] beanDefinitionNames = ac.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
        Object bean = ac.getBean(beanDefinitionName);
        System.out.println("name=" + beanDefinitionName + " object=" + bean);
    }
}
  • 사용자 정의 빈 출력 : BeanDefinition.ROLE_APPLICATION을 활용하여 사용자가 직접 정의한 애플리케이션 빈(Application Bean)만 필터링하여 조회할 수 있다.
// 사용자 정의 빈 출력
@Test @DisplayName("사용자 정의 빈 출력")
void findApplicationBean() {
    String[] beanDefinitionNames = ac.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
        BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);

        // 빈 역할 구분
        // BeanDefinition.ROLE_APPLICATION : 사용자가 정의한 빈
        // BeanDefinition.ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈
        if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
            Object bean = ac.getBean(beanDefinitionName);
            System.out.println("name=" + beanDefinitionName + " object=" + bean);
        }
    }
}

참고 : BeanDefinition.ROLE_APPLICATIONBeanDefinition.ROLE_INFRASTRUCTURE로 변경하면 스프링 내부 빈이 출력된다. 스프링에서 "Application"이라는 용어는 "사용자의 애플리케이션"을 의미하며, ROLE_APPLICATION은 "애플리케이션 개발자(사용자)가 개발하기 위해 작성한 빈"을 의미한다. 반대로 ROLE_INFRASTRUCTURE는 "스프링 프레임워크가 내부적으로 사용하는 빈"을 의미한다.

🚨 빈 조회 시 발생할 수 있는 주요 예외 상황

빈 조회 과정에서 발생할 수 있는 주요 예외들을 이해하고 적절히 처리하는 것이 중요하다.

NoSuchBeanDefinitionException

발생 원인 : 찾으려는 빈이 스프링 컨테이너에 존재하지 않는 경우 발생한다.

해결 방법 :

  • 빈이 제대로 등록(Registered)되었는지 확인한다.
  • 컴포넌트 스캔(Component Scan) 대상에 포함되었는지 확인한다.
  • 조회 시 사용한 빈 이름이나 타입이 올바른지(Correct) 확인한다.

코드 예시 :

@Test
void noSuchBeanTest() {
    // 존재하지 않는 빈 이름으로 조회 시도
    assertThrows(NoSuchBeanDefinitionException.class, () ->
        ac.getBean("wrongName", MemberService.class));
}

NoUniqueBeanDefinitionException

발생 원인 : 같은 타입의 빈이 스프링 컨테이너에 둘 이상(More Than One) 존재하는 경우 발생한다. 단일 빈 조회를 시도할 때 스프링이 어떤 빈을 주입해야 할지 결정할 수 없을 때 발생한다.

해결 방법 :

  • 빈 이름을 지정(Specify Bean Name)하여 조회한다.
  • 특정 빈에 @Primary 어노테이션을 사용하여 기본 빈(Primary Bean)으로 지정한다.
  • @Qualifier 어노테이션을 사용하여 빈 이름을 명시적으로 지정하여 조회한다.

코드 예시 :

@Test
void duplicateBeanTest() {
    // DiscountPolicy 타입의 빈이 여러 개일 때 예외 발생
    assertThrows(NoUniqueBeanDefinitionException.class, () ->
        ac.getBean(DiscountPolicy.class));

    // 해결 방법 : 빈 이름 지정
    DiscountPolicy rateDiscountPolicy =
        ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
}

BeanCreationException

발생 원인 : 빈이 생성되는 과정에서 문제가 발생한 경우.

주요 원인 :

  • 순환 참조(Circular Dependency) : 빈들 간에 서로를 의존하는 구조가 발생할 때.
  • 필수 의존성 누락(Missing Required Dependency) : 특정 빈이 필요로 하는 다른 빈이 컨테이너에 없는 경우.
  • 초기화 메서드 실패(Initialization Method Failure) : 빈의 초기화 로직(예 : @PostConstruct)에서 예외가 발생한 경우.

해결 방법 :

  • 순환 참조(Circular Dependency)를 제거한다.
  • 필수 의존성이 컨테이너에 제대로 제공(Provided)되었는지 확인한다.
  • 빈의 초기화 로직을 검토(Review)하고 오류를 수정한다.

코드 예시 :

@Test
void beanCreationExceptionTest() {
    // 순환 참조가 있는 설정 클래스로 컨텍스트 생성 시도 시 예외 발생
    assertThrows(BeanCreationException.class, () ->
        new AnnotationConfigApplicationContext(CircularConfig.class));
}

BeanDefinitionStoreException

발생 원인 : 빈 정의(Bean Definition)를 불러오는 과정에서 문제가 발생한 경우.

주요 원인 :

  • XML 설정 파일 문법 오류(Syntax Error in XML Configuration) : XML 기반 설정에서 문법적 오류가 있을 때.
  • 클래스를 찾을 수 없음(Class Not Found) : 빈 정의에 명시된 클래스를 클래스패스에서 찾을 수 없을 때.

해결 방법 :

  • 설정 파일의 문법(Syntax)을 검증한다.
  • 클래스패스(Classpath)를 확인하여 필요한 클래스가 올바르게 위치하는지 확인한다.

✅ Assertion을 이용한 빈 검증

빈 조회 테스트에서는 단순히 빈을 조회하는 것을 넘어서, 조회된 빈이 의도한 대로 존재하는지(Existence), 그리고 올바른 타입(Correct Type)인지 검증(Assertion)하는 것이 매우 중요하다. 이를 위해 Assertion(단언) 기능을 활용할 수 있다.

기본적인 빈 검증

containsBean() 메서드로 빈의 존재 여부를 확인하고, getBean()으로 가져온 빈이 적절한 구현체 타입인지 검증한다.

@Test
void basicBeanAssertion() {
    // containsBean()으로 "memberService" 빈이 존재하는지 검증
    assertThat(ac.containsBean("memberService")).isTrue();

    // getBean()으로 가져온 빈이 MemberServiceImpl 타입의 인스턴스인지 검증
    MemberService memberService = ac.getBean("memberService", MemberService.class);
    assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}

빈 수량 검증

getBeansOfType()을 사용하여 특정 타입의 모든 빈을 조회하고, 그 개수(Count)를 검증하거나, getBeanDefinitionNames()로 등록된 전체 빈의 개수를 확인한다.

@Test
void beanCountAssertion() {
    // getBeansOfType()으로 MemberService 타입의 모든 빈을 조회하여 개수가 1인지 검증
    Map<String, MemberService> beansOfType = ac.getBeansOfType(MemberService.class);
    assertThat(beansOfType.size()).isEqualTo(1);

    // getBeanDefinitionNames()로 등록된 전체 빈 개수가 0보다 큰지 검증
    String[] beanDefinitionNames = ac.getBeanDefinitionNames();
    assertThat(beanDefinitionNames.length).isGreaterThan(0);
}

예외 상황 검증

특정 예외가 예상대로 발생하는지(Expected Exception)를 검증한다. 이는 예외 처리 로직이 올바르게 작동하는지 확인하는 데 유용하다.

@Test
void exceptionAssertion() {
    // 존재하지 않는 빈을 조회할 때 NoSuchBeanDefinitionException 발생 검증
    assertThrows(NoSuchBeanDefinitionException.class, () ->
        ac.getBean("xxxxx", MemberService.class));

    // 동일한 타입의 빈이 여러 개 있을 때 NoUniqueBeanDefinitionException 발생 검증
    assertThrows(NoUniqueBeanDefinitionException.class, () ->
        ac.getBean(DiscountPolicy.class));
}

Assertion 사용 시 주의사항

  • 테스트의 의도(Intention)를 명확히 드러내는 검증문을 작성해야 한다.
  • 테스트 실패 시 원인을 쉽게 파악할 수 있는 명확한 오류 메시지(Clear Error Message)를 제공한다.
  • 불필요한 검증(Unnecessary Assertion)은 피하고 핵심적인 부분만 검증한다.
  • 테스트 코드의 가독성(Readability) 유지를 위해 적절한 들여쓰기와 포맷팅을 사용한다.