본문 바로가기

Development/Spring

[Spring] 스프링 컨테이너에 등록된 스프링 빈 조회

 

지난 글에서 스프링 컨테이너와 빈이 설정되는 것과 관련하여 살펴보았다.

 

이번 글에서 지난번에 설명한 스프링 컨테이너와 그리고 스프링 컨테이너에 등록된 스프링 빈을 조회하는 여러 가지 방법에 대해 살펴보려고 한다. 그러면 컨테이너에 등록된 모든 빈을 조회하는 것부터 해서 코드와 함께 하나하나 살펴보도록 하자

 

컨테이너에 등록된 모든 빈 조회

  • AppConfig.java
@Configuration
public class AppConfig {

    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }

    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }

    @Bean
    public OrderService orderService() {
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    @Bean
    public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
    }

}
public class ApplicationContextInfoTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    @Test
    @DisplayName("모든 빈 출력하기")
    void findAllBean() {
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            Object bean = ac.getBean(beanDefinitionName);
            System.out.println("name = " + beanDefinitionName + " object = " + bean);
        }
    }

    @Test
    @DisplayName("애플리케이션 빈 출력하기")
    void 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);
            }
        }
    }
}

findAllBeanTest의 결과

findApplicationBean의 결과

  • 모든 빈 출력하기
    • 실행하면 스프링에 등록된 모든 빈 정보를 볼 수 있다.
    • getBeanDefinitionNames() : 스프링에 등록된 모든 빈 이름을 조회
    • getBean() : 빈 이름으로 빈 객체(인스턴스)를 조회한다.
  • 애플리케이션 빈 출력하기
    • 스프링이 내부에서 사용하는 빈은 제외하고, 내가 등록한 빈만 출력한다.
    • 스프링이 내부에서 사용하는 빈은 getRole()로 구분할 수 있다.
    • ROLE_APPLICATION : 사용자가 정의한 빈
    • ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈

 

스프링 빈 조회 - 기본

  • 스프링 컨테이너에서 스프링 빈을 찾는 가장 기본적인 조회 방법
    • ac.getBean(빈 이름, 타입)
    • ac.getBean(타입)
    • 조회 대상 스프링 빈이 없으면 예외 발생 -> NoSuchBeanDefinitionException : No bean named "빈 이름" available

이렇게 스프링 컨테이너 내에서 스프링 빈을 찾는 조회 방법에는 두 가지가 있다. 아래에는 이 두 가지 방법을 이용해서 빈 이름으로 조회, 타입으로 조회, 구체적인 타입으로 조회 세 가지의 예시를 코드와 함께 살펴보려고 한다.

 

  • 빈 이름으로 조회
public class ApplicationContextBasicFindTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    @Test
    @DisplayName("빈 이름으로 조회")
    void findBeanByName() {
        MemberService memberService = ac.getBean("memberService", MemberService.class);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }
}

결과

 

  • 타입으로 조회
public class ApplicationContextBasicFindTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    @Test
    @DisplayName("이름 없이 타입으로만 조회")
    void findBeanByType() {
        MemberService memberService = ac.getBean(MemberService.class);
        System.out.println("memberService = " + memberService);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }
}

결과

 

  • 구체 타입으로 조회
public class ApplicationContextBasicFindTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    @Test
    @DisplayName("구체 타입으로 조회")
    void findBeanByName2() {
        MemberService memberService = ac.getBean("memberService", MemberServiceImpl.class);
        System.out.println("memberService = " + memberService);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }
}

결과

 

스프링 빈 조회 - 상속 관계

부모 타입으로 스프링 빈을 조회하면 자식 타입도 함께 조회된다. -> Object 타입으로 조회 시 모든 스프링 빈을 조회한다.(Object는 모든 자바 객체의 최고 부모 타입)

 

  • 자식이 둘 이상인 부모 타입으로 조회
public class ApplicationContextExtendsFindTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

    @Test
    @DisplayName("부모 타입으로 조회 시, 자식이 둘 이상 있으면, 중복 오류가 발생한다.")
    void findBeanByParentTypeDuplicate() {

        ac.getBean(DiscountPolicy.class);
    }
    
    @Configuration
    static class TestConfig {
        @Bean
        public DiscountPolicy rateDiscountPolicy() {
            return new RateDiscountPolicy();
        }

        @Bean
        public DiscountPolicy fixDiscountPolicy() {
            return new FixDiscountPolicy();
        }
    }
    
}

결과

해당 테스트 코드를 실행시키면 위에 결과와 같이 NoUniqueBeanDefinitionException 예외가 발생하는데 오류 내용을 전체적으로 보면 org.springframework.beans.factory.NoUniqueBeanDefinitionExcepti on: No qualifying bean of type 'hello.core.discount.DiscountPolicy' available: expected single matching bean but found 2: rateDiscountPolicy, fixDiscountPolicy로 DiscountPolicy라는 부모 타입의 빈을 조회하면 rateDiscountPolicy, fixDiscountPolicy 2가지 자식 타입이 조회되어 발생하는 중복 오류이다.

 

  • 자식이 둘 이상인 부모 타입의 경우 특정 빈 이름으로 조회
public class ApplicationContextExtendsFindTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

    @Test
    @DisplayName("부모 타입으로 조회 시, 자식이 둘 이상 있으면, 빈 이름을 지정하면 된다.")
    void findBeanByParentTypeBeanName() {
        DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
        System.out.println("rateDiscountPolicy = " + rateDiscountPolicy);
        assertThat(rateDiscountPolicy).isInstanceOf(DiscountPolicy.class);
    }
    
    @Configuration
    static class TestConfig {
        @Bean
        public DiscountPolicy rateDiscountPolicy() {
            return new RateDiscountPolicy();
        }

        @Bean
        public DiscountPolicy fixDiscountPolicy() {
            return new FixDiscountPolicy();
        }
    }
    
}

결과

이처럼 부모 타입으로 조회 시 자식 타입이 둘 이상일 경우에는 자식 타입들에 대해서 특정 빈 이름을 지정해 주면 해당 빈을 조회를 해준다.(현재 테스트에 대해선 RateDiscountPolicy를 조회)

 

  • 특정 하위 타입으로 빈 조회
public class ApplicationContextExtendsFindTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

	@Test
    @DisplayName("특정 하위 타입으로 조회")
    void findBeanBySubType() {
        RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
        System.out.println("bean = " + bean);
        assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
    }
    
    @Configuration
    static class TestConfig {
        @Bean
        public DiscountPolicy rateDiscountPolicy() {
            return new RateDiscountPolicy();
        }

        @Bean
        public DiscountPolicy fixDiscountPolicy() {
            return new FixDiscountPolicy();
        }
    }
    
}

결과

 

  • 부모 타입으로 모든 빈 조회
public class ApplicationContextExtendsFindTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

    @Test
    @DisplayName("부모 타입으로 모두 조회하기")
    void findAllBeanByParentType() {
        Map<String, DiscountPolicy> beansOfType = ac.getBeansOfType(DiscountPolicy.class);
        assertThat(beansOfType.size()).isEqualTo(2);
        for (String key : beansOfType.keySet()) {
            System.out.println("key = " + key + " value = " + beansOfType.get(key));
        }
    }

    @Test
    @DisplayName("부모 타입으로 모두 조회하기 - Object")
    void findAllBeanByObjectType() {
        Map<String, Object> beansOfType = ac.getBeansOfType(Object.class);
        for (String key : beansOfType.keySet()) {
            System.out.println("key = " + key + " value = " + beansOfType.get(key));
        }
    }

    @Configuration
    static class TestConfig {
        @Bean
        public DiscountPolicy rateDiscountPolicy() {
            return new RateDiscountPolicy();
        }

        @Bean
        public DiscountPolicy fixDiscountPolicy() {
            return new FixDiscountPolicy();
        }
    }

}

결과

이번 테스트의 경우에는 TestConfig에 정의된 DiscountPolicy로 조회한 경우와 Object로 조회한 경우에 대한 결괏값을 모두 출력해 봤다.

 

먼저 DiscountPolicy의 경우 결과 사진 중 현재 결과처럼 DiscountPolicy의 자식인 RateDiscountPolicy와 FixDiscountPolicy 두 개의 빈이 Map을 이용해 조회되는 것을 알 수가 있다.

해당 결과의 경우 Object라는 모든 자바 객체의 최고 부모 타입을 조회했기 때문에 해당 객체의 자식 객체인 빈들이 모두 조회되는 것을 알 수가 있다.(RateDiscountPolicy, FixDiscountPolicy 역시 포함되어 있는 걸 알 수가 있다.)

 

출처 : 인프런 우아한 형제들 최연소 기술이사 김영한의 스프링 완전 정복(스프링 핵심원리 - 기본 편)