[Spring] Spring bean 조회

노유성·2023년 7월 13일
0
post-thumbnail

Spring Container 생성

 ApplicationContext ac
            = new AnnotationConfigApplicationContext(AppConfig.class);

ApplicationContext 를 스프링 컨테이너라고 하며 interface이다. 스프링 컨테이너는 XML 기바능로 만들 수 있고, annotation 기반으로 만들 수 있다. 이전까지 우리가 사용한 방식이 annotation 기반으로 만든 것이다.
즉 위에서 AnnotationConfigApplicationContext는 스프링 컨테이너 interface의 구현체이다.

더 정확히는 스프링 컨테이너를 BeanFactory, ApplicationContext를 구분해서 이야기한다. 하지만 일반적으로는 BeanFactory를 직접 사용하는 경우는 거의 없다.

Spring Container 생성 과정


Spring Container를 생성할 때는 구성 정보를 지정해주어야 한다. 그래서 AppConfig.class를 지정해주었다. 여기서 구성 정보란, bean을 등록할 때 어떤 이름으로 어떤 객체를 등록할 것인지를 나타낸 정보이다.

빈 이름은 메소드 이름이며, 직접 부여할 수도 있다.

그래서 생성 과정을 자세히 들여다보면,

제일 먼저 스프링 컨테이너를 생성하고

의존 관계를 주입한다. 여기서 이 과정이 단순히 java code를 호출하는 것이 아니다.

Container Bean 조회

모든 출력하기

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("bean = " + bean);
    }
}


우리가 설정한 Bean 외에도 스프링 컨테이너에서 기본적으로 등록하는 빈들도 출력된다.

등록한 빈만 조회

@Test
@DisplayName("어플리케이션 빈 조회하기")
void findApplicationBean() {
    String[] beanDefinitionNames = ac.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
        BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);

        if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
            Object bean = ac.getBean(beanDefinitionName);
            System.out.println("bean = " + bean);

        }
    }
}


bean의 역할이 ROLE_APPLICATION 인지 아닌지 if문으로 걸러내면 우리가 등록한 빈만 조회할 수 있다.

  • ROLE_APPLICATION: 사용자가 정의한 빈
  • ROLE_INFRASTRUCTURE: 스프링 내부에서 사용하는 빈

빈 조회 - 기본

ac.getBean(빈이름, 타입)
ac.getBean(타입)

위 두 가지가 빈을 찾는 가장 기본적인 조회 방법이다.

조회 대상이 없으면 exception이 발생한다.

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

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

위와 같이 타입만으로 조회할 수도 있고 빈 이름도 더해서 찾을 수도 있다. 우리가 AppConfig에 등록한 빈들을 다시 생각해보면 대부분 type이 interface이다(당연하게도). 하지만 bean을 구체 타입으로도 찾을 수 있다. 즉 등록한 시점에 우리가 사용하기로한 구현체로 찾을 수 있다는 뜻이다.

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

위와 같이 구체 타입으로 조회할 수 있고 만약에 존재하지 않은 빈을 조회하려고 시도하면

@Test
@DisplayName("빈 이름으로 조회")
void findBeanByNameX() {
    org.junit.jupiter.api.Assertions.assertThrows(NoSuchBeanDefinitionException.class,
            () -> ac.getBean("xxxxxx", MemberService.class));
}

exception이 발생한다.

같은 타입 조회

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

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

만약에 위와 같이 같은 타입으로 빈들이 있는데 조회를 하려고 하면 어떻게 될까

@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다.")
void findBeanFindByTypeDupl() {
    assertThrows(NoUniqueBeanDefinitionException.class,
            () -> ac.getBean(MemberRepository.class));
}
@Test
@DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 빈 이름을 지정하면 된다.")
void findBeanFindByName() {
    MemberRepository bean 
    	= ac.getBean("memberRepository1",MemberRepository.class);
}

첫번째 예시처럼 type만으로 빈을 조회하는데 여러 개가 조회가 되면 중복 오류가 발생하므로 두번째 예시처럼 빈의 이름을 지정해서 조회하면 된다.

그러다가 특정 type의 빈을 모두 조회하고 싶다면

@Test
@DisplayName("특정 타입을 모두 조회하기")
void findAllBeanByType() {
    Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
    for (String key : beansOfType.keySet()) {
        System.out.println("key = " + key + "values = " + beansOfType.get(key));
    }

    System.out.println("beansofType = " + beansOfType);
    org.assertj.core.api.Assertions.assertThat(beansOfType.size()).isEqualTo(2);
}

getBeansOfType() 메소드를 이용하면 된다.

부모 타입으로 조회

부모 타입으로 조회를 하면은 자식 타입도 모두 조회된다. 그래서 java의 모든 객체의 최고 부모인 Object 타입으로 조회를 하면은 모든 스프링 빈을 조회할 수 있다.

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

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

AnnotationConfigApplicationContext ac
        = new AnnotationConfigApplicationContext(TestOcnfig.class);
@Test
@DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면 중복 오류가 발생.")
void findBeanByParentTypeDuplicate() {
//        DiscountPolicy bean = ac.getBean(DiscountPolicy.class); // 오류 발생
    Assertions.assertThrows(NoUniqueBeanDefinitionException.class,
            () -> ac.getBean(DiscountPolicy.class));
}

부모 타입으로 조회를 시도했는데 만약에 자식이 둘 이상이면은 중복 오류가 발생한다.

그래서 그럴 때는 아래와 같이 자식 bean의 이름을 지정해주면 된다.

@Test
@DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면 빈 이름을 지정하면 된다.")
void findBeanByParentTypeBeanName() {
    DiscountPolicy rateDiscountPolicy
    	= ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
    org.assertj.core.api.Assertions.assertThat(rateDiscountPolicy)
    	.isInstanceOf(RateDiscountPolicy.class);
}

혹은 부모 타입으로 자식 빈들까지 모두 조회를 하고 싶으면 getBeansOfType 메소드를 사용하면 된다.

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

그리고 마지막으로 Object 타입으로 조회 시에는 모든 빈이 조회가 된다.

@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));
    }
}

profile
풀스택개발자가되고싶습니다:)

0개의 댓글