Spring Boot

빈(Bean)을 조회하는 다양한 방법

작은별._. 2023. 11. 30. 22:04
728x90
반응형

아래 포스팅에서 스프링 빈을 직접 조회하는 방법에 대해서 알아봤습니다. 아래 포스팅에서는 이름을 통해 빈을 찾았다면 이번 포스팅에서는 빈을 조회하는 다른 여러 가지 방법에 대해서 코드로 작성해 보았습니다.

 

https://silver-programmer.tistory.com/entry/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88%EC%99%80-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B9%88Bean

 

스프링 컨테이너와 스프링 빈(Bean)

스프링 컨테이너란, 스프링 빈(Bean)을 저장하고 관리하는 저장소입니다. 스프링 빈(Bean)은 스프링 컨테이너에 의해 관리되는 재사용 가능한 소프트웨어 컴포넌트입니다. 즉, 스프링 컨테이너가

silver-programmer.tistory.com

 

이번 포스팅은 위 포스팅에서 작성한 코드를 이어서 사용하니 위의 포스팅을 먼저 보고 읽는 것이 좋을 것 같습니다!! 

 

더보기
// AppConfig.class
public class AppConfig {
    @Bean
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }

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

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

}

 

<!--appConfig.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="memberService" class="hello.core.member.MemberServiceImpl">
        <constructor-arg name="memberRepository" ref="memberRepository" />
    </bean>

    <bean id="memberRepository"
          class="hello.core.member.MemoryMemberRepository" />

    <bean id="orderService" class="hello.core.order.OrderServiceImpl">
        <constructor-arg name="memberRepository" ref="memberRepository" />
        <constructor-arg name="discountPolicy" ref="discountPolicy" />
    </bean>

    <bean id="discountPolicy" class="hello.core.discount.RateDiscountPolicy" />
</beans>

 

class CoreApplicationTests {
    AnnotationConfigApplicationContext ac = new
            AnnotationConfigApplicationContext(AppConfig.class);


    @Test
    @DisplayName("빈 이름으로 조회")
    void findBeanByName() {
        OrderService orderService = (OrderService) ac.getBean("orderService");
        assertThat(orderService).isInstanceOf(OrderService.class);

    }

    @Test
    @DisplayName("이름 없이 타입만으로 조회")
    void findBeanByType() {
        OrderService memberService = ac.getBean(OrderService.class);
        assertThat(memberService).isInstanceOf(OrderServiceImpl.class);
    }

    @Test
    @DisplayName("구체 타입으로 조회")
    void findBeanByConcreteType() {
        OrderServiceImpl memberService = ac.getBean("orderService",
                OrderServiceImpl.class);
        assertThat(memberService).isInstanceOf(OrderServiceImpl.class);
    }

    @Test
    @DisplayName("등록되지 않은 빈 이름으로 조회 시 에러를 내 뱉는다.")
    void findBeanByNameX() {
        assertThrows(NoSuchBeanDefinitionException.class, () ->
                ac.getBean("noBean", OrderService.class));
    }


    AnnotationConfigApplicationContext ac2 = new
            AnnotationConfigApplicationContext(TestConfig.class);

    @Configuration
    static class TestConfig {
        @Bean
        public MemberRepository memberRepository1() {
            return new MemoryMemberRepository();
        }

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

    }

    @Test
    @DisplayName("타입으로 조회시 같은 타입을 가진 빈이 둘 이상이면, 에러를 내 뱉는다.")
    void findBeanByTypeDulicated() {
        assertThrows(NoUniqueBeanDefinitionException.class, () ->
                ac2.getBean(MemberRepository.class));
    }

    @Test
    @DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 빈 이름을 지정하면 된다")
    void findBeanByTypeWithName() {
        MemberRepository memberRepository = ac2.getBean("memberRepository1", MemberRepository.class);
        assertThat(memberRepository).isInstanceOf(MemberRepository.class);
    }

    @Test
    @DisplayName("특정 타입을 모두 조회")
    void findAllBeanByType() {
        // getBeansOfType()을 사용하면 해당 타입의 모든 빈을 조회할 수 있다.
        Map<String, MemberRepository> beansOfType = ac2.getBeansOfType(MemberRepository.class);
        for (String key : beansOfType.keySet()) {
            System.out.println("key = " + key + " value = " +
                    beansOfType.get(key));
        }
        System.out.println("beansOfType = " + beansOfType);
        assertThat(beansOfType.size()).isEqualTo(2);
    }


    @Test
    @DisplayName("빈 설정 메타정보 확인")
    void findApplicationBean() {
        String[] beanDefinitionNames = ac2.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition =
                    ac2.getBeanDefinition(beanDefinitionName);
            if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
                System.out.println("beanDefinitionName = " + beanDefinitionName + ", " +
                        " beanDefinition = " + beanDefinition);
            }
        }

    }
 }

 

 

특히 가장 아래 메소드는 BeanDefinition을 이용하여 어떤 정보를 볼 수 있는지 확인할 수 있습니다. 출력결과 아래와 같은 정보를 볼 수 있습니다.


beanDefinitionName = coreApplicationTests.TestConfig,  beanDefinition = Generic bean: class [hello.core.CoreApplicationTests$TestConfig$$SpringCGLIB$$0]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null
beanDefinitionName = memberRepository1,  beanDefinition = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=coreApplicationTests.TestConfig; factoryMethodName=memberRepository1; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in hello.core.CoreApplicationTests$TestConfig
beanDefinitionName = memberRepository2,  beanDefinition = Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=coreApplicationTests.TestConfig; factoryMethodName=memberRepository2; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in hello.core.CoreApplicationTests$TestConfig

 


각각의 속성들을 정리해 보았습니다.

  1. BeanClassName: 생성할 빈의 클래스 명 (자바 설정처럼 팩토리 역할의 빈을 사용하면 null입니다.)
  2. scope: 싱글톤 (기본값)
  3. lazyInit: 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때까지 최대한 생성을 지연 처리 하는지 여부
  4. factoryBeanName: 팩토리 역할의 빈을 사용할 경우 이름 (ex. appConfig, TestConfig)
  5. factoryMethodName: 빈을 생성할 팩토리 메서드 지정 (ex. memberRepository1, memberRepository2)
  6. InitMethodName: 빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명
  7. DestroyMethodName: 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명
  8. Constructor arguments, Properties: 의존관계 주입에서 사용 (자바 설정처럼 팩토리 역할의 빈을 사용하면 없습니다.)

스프링 컨테이너는 BeanDefinition에만 의존하도록 설계되어 있습니다. 즉, 개발자가 직접 BeanDefintion 구현체를 위와 같이 만들어 정의할 수 있습니다. (실제로는 거의 하는 일이 없지만요.)


 

 

@Bean 또는 설정정보 하나당(<bean>), 하나의 메타정보를 생성하여 BeanDefinition을 만듭니다. 스프링 컨테이너는 실제 설정 정보가 아닌 메타정보(BeanDefinition)만을 보고 이를 기반으로 스프링 빈을 생성합니다.


 

 

  • 위 그림에서, AnnotationConfigApplicationContext는 AnnotatedBeanDefinitionReader를 사용해 AppConfig.class를 읽고 BeanDefinition을 생성합니다.
  • GenericXmlApplicationContext는 XmlBeanDefinitionReader를 사용해 appConfig.xml 설정 정보를 읽고 BeanDefinition을 생성합니다. 
  • 새로운 형식의 설정 정보가 추가되면, XxxBeanDefinitionReader를 만들어서 BeanDefinition을 생성하 면 된다

이렇게 BeanDefinition을 직접 생성해서 스프링 컨테이너에 등록할 수 있지만, 실무에서 이렇게 직접 정의하거나 사용할 일은 거의 없다고 합니다. 따라서 BeanDefinition에 대해서 너무 깊이 있게 이해하려고 하기보다는, 스프링이 다양한 형태의 설정 정보를 BeanDefinition으로 추상화해서 사용한다는 정도만 이해하면 될 것 같습니다. 


[전체 소스코드]

 

GitHub - eunhwa99/Spring

Contribute to eunhwa99/Spring development by creating an account on GitHub.

github.com

 

 

[참고자료]

김영한, "스프링 핵심 원리 - 기본편", 인프런

 

728x90
반응형