본문 바로가기

SPRING/Spring

[스프링| 스프링 핵심 원리 | 기본편 | 스프링 컨테이너와 스프링 빈]스프링 컨테이너에 등록된 빈 조회

public class ApplicationContextBasicFindTest {  // 스프링 빈 조회 기능을 테스트하는 클래스 정의

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);  // 스프링 컨테이너를 초기화하고, AppConfig 클래스의 설정 정보를 로딩

    @Test  // JUnit 테스트 메소드임을 나타내는 어노테이션
    @DisplayName("빈 이름으로 조회")  // 테스트의 이름을 설정
    void findBeanByName() {  // 빈 이름으로 조회하는 테스트 메서드
        MemberService memberService = ac.getBean("memberService", MemberService.class);  // "memberService" 이름의 빈을 MemberService 타입으로 조회
        assertThat(memberService).isInstanceOf(MemberService.class);  // 조회된 빈이 MemberService 타입인지 검증
    }

    @Test  
    @DisplayName("이름 없이 타입으로만 조회")  
    void findBeanByType() {  // 이름 없이 타입만으로 빈을 조회하는 테스트 메서드
        MemberService memberService = ac.getBean(MemberService.class);  // MemberService 타입의 빈을 조회
        assertThat(memberService).isInstanceOf(MemberService.class);  // 조회된 빈이 MemberService 타입인지 검증
    }

    @Test  
    @DisplayName("구체 타입으로 조회")  // 구체적인 클래스 타입으로 빈을 조회하는 테스트
    void findBeanByName2() {  
        MemberService memberService = ac.getBean("memberService", MemberServiceImpl.class);  // "memberService" 이름의 빈을 MemberServiceImpl 타입으로 조회
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);  // 조회된 빈이 MemberServiceImpl 타입인지 검증
    }

    @Test  
    @DisplayName("빈 이름으로 조회")  
    void findBeanByNameX() {  // 존재하지 않는 빈 이름으로 조회 시 예외를 테스트하는 메서드
        //ac.getBean("xxxxx", MemberService.class);
        assertThrows(NoSuchBeanDefinitionException.class,
                () -> ac.getBean("xxxxx", MemberService.class));  // "xxxxx" 이름의 빈이 존재하지 않을 경우 예외가 발생하는지 검증
    }
}

class ApplicationContextInfoTest {  // ApplicationContextInfoTest 클래스 선언

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);  // Spring 컨테이너 초기화. AppConfig 클래스를 설정 정보로 사용

    @Test  // JUnit 테스트 케이스 선언
    @DisplayName("모든 빈 출력하기")  // 테스트 케이스의 이름을 지정
    void findAllBean() {  // findAllBean 테스트 메서드 시작
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();  // 컨테이너에 등록된 모든 빈의 이름을 가져옴
        for (String beanDefinitionName : beanDefinitionNames) {  // 빈 이름 목록을 순회
            Object bean = ac.getBean(beanDefinitionName);  // 빈 이름에 해당하는 빈 객체를 조회
            System.out.println("name = " + beanDefinitionName + " object =" + bean);  // 빈 이름과 빈 객체를 출력
        }
    }

    @Test  // 또 다른 JUnit 테스트 케이스 선언
    @DisplayName("애플리케이션 빈 출력하기")  // 테스트 케이스의 이름을 지정
    void findApplicationBean() {  // findApplicationBean 테스트 메서드 시작
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();  // 컨테이너에 등록된 모든 빈의 이름을 가져옴
        for (String beanDefinitionName : beanDefinitionNames) {  // 빈 이름 목록을 순회
            BeanDefinition beanDefinition =  // 빈의 정의를 조회
                    ac.getBeanDefinition(beanDefinitionName);
            
            //Role ROLE_APPLICATION: 직접 등록한 애플리케이션 빈
            //Role ROLE_INFRASTRUCTURE: 스프링이 내부에서 사용하는 빈
            if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {  // 빈의 역할이 애플리케이션 빈인지 확인
                Object bean = ac.getBean(beanDefinitionName);  // 빈 이름에 해당하는 빈 객체를 조회
                System.out.println("name=" + beanDefinitionName + " object=" + bean);  // 빈 이름과 빈 객체를 출력
            }
        }
    }
}

getBean()

getBean() 메서드는 스프링의 ApplicationContext 인터페이스에서 제공하는 메서드로, 스프링 컨테이너에서 빈(bean)을 검색할 때 사용된다. 이 메서드는 스프링이 관리하는 빈의 인스턴스를 런타임 시에 가져올 수 있도록 해준다. 사용자는 getBean()을 호출하여 빈의 인스턴스를 직접 요청하고, 스프링 컨테이너는 해당 빈을 찾아 반환한다.

getBean() 메서드의 주요 형태와 사용법

  1. 이름으로 빈 조회: getBean(String name) 형태로 사용하며, 빈의 이름을 문자열로 지정하여 해당 빈을 검색한다. 반환 타입은 Object이므로, 적절한 타입으로 캐스팅이 필요하다.
  2. 이름과 타입으로 빈 조회: getBean(String name, Class<T> requiredType)을 사용하면, 이름과 함께 요구하는 타입을 명시할 수 있다. 이 메서드는 요청한 타입의 빈을 반환하므로, 타입 캐스팅이 필요 없다.
  3. 타입만으로 빈 조회: getBean(Class<T> requiredType)은 주어진 타입의 빈을 찾아 반환한다. 같은 타입의 빈이 여러 개 있을 경우, NoUniqueBeanDefinitionException이 발생할 수 있다.

예외 처리

  • NoSuchBeanDefinitionException: 요청한 이름이나 타입의 빈이 컨테이너에 존재하지 않을 때 발생한다.
  • NoUniqueBeanDefinitionException: 요청한 타입의 빈이 여러 개 존재하고, 구체적인 빈을 지정하지 않았을 때 발생한다.
  • BeanNotOfRequiredTypeException: 요청한 빈이 존재하지만, 요청한 타입과 일치하지 않을 때 발생한다.

getBean() 사용 시 고려사항

getBean() 메서드는 유연성과 직접적인 제어를 제공하지만, 스프링의 의존성 주입(Dependency Injection) 기능을 통해 자동으로 빈을 주입받는 것이 권장된다. getBean()을 직접 사용하는 것은 코드의 결합도를 높일 수 있으며, 의존성 주입의 장점을 누리기 어렵게 만든다. 따라서 테스트 코드나 특정 상황에서 필수적인 경우에 한정하여 사용하는 것이 좋다.

 

public class ApplicationContextSameBeanFindTest {  // 테스트 클래스 정의

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);  // 스프링 컨테이너를 초기화하고 SameBeanConfig 설정 클래스를 사용

    @Test  // JUnit 테스트 메서드임을 나타내는 어노테이션
    @DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다") 
    void findBeanByTypeDuplicate() {  // 동일한 타입의 빈이 여러 개 있을 때 발생하는 예외를 테스트하는 메서드
        //MemberRepository bean = ac.getBean(MemberRepository.class);
        assertThrows(NoUniqueBeanDefinitionException.class, () ->
                ac.getBean(MemberRepository.class));  // 동일한 타입의 빈을 조회할 때 예외 발생을 확인
    }

    @Test  
    @DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 빈 이름을 지정하면 된다") 
    void findBeanByName() {  // 동일한 타입의 빈 중 특정 이름으로 빈을 조회하는 메서드
        MemberRepository memberRepository = ac.getBean("memberRepository1",
                MemberRepository.class);  // "memberRepository1" 이름의 빈을 조회
        assertThat(memberRepository).isInstanceOf(MemberRepository.class);  // 조회된 빈이 MemberRepository 타입인지 확인
    }

    @Test  
    @DisplayName("특정 타입을 모두 조회하기") 
    void findAllBeanByType() {  // 동일한 타입의 모든 빈을 조회하는 메서드
        Map<String, MemberRepository> beansOfType =
                ac.getBeansOfType(MemberRepository.class);  // MemberRepository 타입의 모든 빈을 Map 형태로 가져옴
        for (String key : beansOfType.keySet()) {  // Map의 모든 키(빈의 이름)를 순회
            System.out.println("key = " + key + " value = " +
                    beansOfType.get(key));  // 각 키와 해당 키의 빈 객체를 출력
        }
        System.out.println("beansOfType = " + beansOfType);  // 전체 Map 출력
        assertThat(beansOfType.size()).isEqualTo(2);  // Map의 크기가 2인지 확인 (두 개의 빈이 있는지)
    }

    @Configuration  // 스프링 설정 클래스임을 나타내는 어노테이션
    static class SameBeanConfig {  // 테스트용 설정 클래스
        @Bean  // 스프링 빈으로 등록함을 나타내는 어노테이션
        public MemberRepository memberRepository1() {  // "memberRepository1" 이름의 빈 등록 메서드
            return new MemoryMemberRepository();  // MemoryMemberRepository 인스턴스 반환
        }

        @Bean  // 다른 빈을 등록
        public MemberRepository memberRepository2() {  // "memberRepository2" 이름의 빈 등록 메서드
            return new MemoryMemberRepository();  // MemoryMemberRepository 인스턴스 반환
        }
    }
}

public class ApplicationContextExtendsFindTest {  // 스프링 빈을 특정 타입으로 조회하는 테스트 클래스 정의

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);  // AppConfig 설정을 사용하여 스프링 컨텍스트를 초기화

    @Test  // JUnit 테스트 메소드 선언
    @DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 중복 오류가 발생한다") 
    void findBeanByParentTypeDuplicate() {  // 부모 타입으로 빈을 조회할 때 같은 타입의 자식 빈이 두 개 이상 있으면 발생하는 오류를 테스트
        assertThrows(NoUniqueBeanDefinitionException.class, () ->
                ac.getBean(DiscountPolicy.class));  // DiscountPolicy 타입의 빈을 조회하려 할 때 예외 발생 여부 확인
    }

    @Test  
    @DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 빈 이름을 지정하면 된다") 
    void findBeanByParentTypeBeanName() {  // 부모 타입으로 조회시 빈 이름을 지정하여 정확한 빈을 조회
        DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy",
                DiscountPolicy.class);  // "rateDiscountPolicy" 이름으로 DiscountPolicy 타입의 빈을 조회
        assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);  // 조회된 빈이 RateDiscountPolicy의 인스턴스인지 확인
    }

    @Test  
    @DisplayName("특정 하위 타입으로 조회") 
    void findBeanBySubType() {  // 특정 하위 타입으로 빈을 직접 조회
        RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);  // RateDiscountPolicy 타입의 빈을 직접 조회
        assertThat(bean).isInstanceOf(RateDiscountPolicy.class);  // 조회된 빈이 RateDiscountPolicy 타입인지 확인
    }

    @Test  
    @DisplayName("부모 타입으로 모두 조회하기") 
    void findAllBeanByParentType() {  // 부모 타입의 모든 빈을 조회
        Map<String, DiscountPolicy> beansOfType =
                ac.getBeansOfType(DiscountPolicy.class);  // DiscountPolicy 타입의 모든 빈을 Map으로 조회
        assertThat(beansOfType.size()).isEqualTo(2);  // 조회된 빈의 수가 2개인지 확인
        for (String key : beansOfType.keySet()) {  // 조회된 빈의 이름과 객체를 출력
            System.out.println("key = " + key + " value=" +
                    beansOfType.get(key));
        } }

    @Test  
    @DisplayName("부모 타입으로 모두 조회하기 - Object") 
    void findAllBeanByObjectType() {  // Object 타입으로 모든 빈을 조회
        Map<String, Object> beansOfType = ac.getBeansOfType(Object.class);  // Object 타입의 모든 빈을 Map으로 조회
        for (String key : beansOfType.keySet()) {  // 조회된 빈의 이름과 객체를 출력
            System.out.println("key = " + key + " value=" +
                    beansOfType.get(key));
        } }

    @Configuration  // 스프링 설정 클래스 선언
    static class TestConfig {  // 테스트용 설정 클래스
        @Bean  // 스프링 빈으로 등록
        public DiscountPolicy rateDiscountPolicy() {  // "rateDiscountPolicy" 이름의 DiscountPolicy 타입 빈을 등록
            return new RateDiscountPolicy();  // RateDiscountPolicy 객체 반환
        }

        @Bean  // 또 다른 스프링 빈으로 등록
        public DiscountPolicy fixDiscountPolicy() {  // "fixDiscountPolicy" 이름의 DiscountPolicy 타입 빈을 등록
            return new FixDiscountPolicy();  // FixDiscountPolicy 객체 반환
        }
    }
}