Spring Bean 조회

강정우·2023년 10월 31일
0

Spring-boot

목록 보기
7/73
post-thumbnail

컨테이너에 등록된 빈 조회

  • 위에서 등록시킨 빈이 잘 등록이 되었는지 조회해보자.

  • 테스트 환경에서 조회 함수를 작성해보자.
    이때 Junit 5 이후 버전은 public을 쓰지 않아도 된다.

  • 또 윗 줄에 배열이 있으면 iter IDEA 예약어를 작성하면 자동으로 for문을 작성해준다.

  • 또 sout 뒤에 m,p,v 가 있는데 m은 메서드 명, v는 value명 p는 parameter 명을 추가하여 print문을 자동으로 생성해준다.
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
  • 빈 메타데이터를 가져오는 함수

  • beanDefinition Role에서 주로 ROLE_APPLICATION을 쓰고 이게 스프링 내부에서 등록한 빈이 아닌 내가 애플리케이션을 개발하기위해서 등록한 빈이나 외부 라이브러리들 이라고 생각하면 된다.
package hello.core.beanfind;

import hello.core.AppConfig;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
// 참고로 Junit 5이후부터는 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);
            if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION){
                Object bean = ac.getBean(beanDefinitionName);
                System.out.println("name = " + beanDefinitionName + "object = " + bean);
            }
        }
    }
}
  • 그래서 위 코드를 실행하면 AppConfig의 Configuartion의 어노테이션까지 포함하여 총 5개의 bean이 등록되어있는 것을 확인할 수 있다.

  • 정리하면
    ac.getBeanDefinitionNames() : 스프링에 등록된 모든 빈 이름을 조회한다.
    ac.getBean(빈이름?, 타입!) : 빈 이름으로 빈 객체를 조회한다.
    beanDefinition.getRole()==BeanDefinition.ROLE_APPLICATION : 스프링에서 사용자가 정의한 빈
    beanDefinition.getRole()==BeanDefinition.ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈
import static org.assertj.core.api.Assertions.*;

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

    @Test
    @DisplayName("빈 타입으로 조회")
    void findBeanByType() {
        MemberService memberService = ac.getBean(MemberService.class);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }

    @Test
    @DisplayName("구체 타입으로 조회")
    void findBeanByName2() {
        MemberService memberService = ac.getBean("memberService", MemberServiceImpl.class);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }
    
    @Test
    @DisplayName("구체 타입으로 조회 실패")
    void findBeanByNameX() {
//        MemberService memberService = ac.getBean("xxxxx", MemberServiceImpl.class);
        assertThrows(NoSuchBeanDefinitionException.class, () -> ac.getBean("XXX", MemberService.class));
    }
}


  • 이때 3번의 경우는 별로 좋지 못 하다. 역할과 구현을 구분해야한다고 했는데 3번은 구현에 의존하는 테스트코드이기 때문이다.

만약 동일한 타입이 둘 이상인 경우..

  • 타입으로 getBean()을 사용할 때 같은 타입의 스프링 빈이 둘 이상이면 오류가 발생한다. 이때는 빈 이름을 지정하자.

  • ac.getBeansOfType()을 사용하면 해당 타입의 모든 빈을 조회할 수 있다.

public class ApplicationContextSameBeanFindTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);

    @Test
    @DisplayName("타입으로 조회시 같은 타입이 둘 이상있으면, 중복 오류가 발생한다")
    void findBeanByTypeDuple() {
//        MemberRepository bean = ac.getBean(MemberRepository.class);
        assertThrows(NoUniqueBeanDefinitionException.class, () -> ac.getBean(MemberRepository.class));
    }

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

    @Test
    @DisplayName("특정 타입을 모두 조회하기")
    void findAllBEanByType() {
        Map<String, MemberRepository> beansOfType = ac.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);
    }

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

}

스프링 빈 조회 - 상속관계

  • 대원칙 : 부모타입으로 조회하면, 자식 타입은 모두 조회된다.
    즉, 모든 스프링 빈을 조회하고 싶다면 모든 객체의 부모인 Object를 조회하면 된다.
package hello.core.beanfind;

import hello.core.discount.DiscountPolicy;
import hello.core.discount.FixDiscountPolicy;
import hello.core.discount.RateDiscountPolicy;
import hello.core.member.MemberService;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class ApplicationContextExtendsFindTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

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

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

    // 물론 앞서 언급 했던 것 처럼 좋지는 않은 방법임.
    @Test
    @DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 특정 하위타입으로 조회해도 된다.")
    void findBeanByParentSubTypeDupli() {
        RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
        assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
    }

    // 참고로 나중에는 시스템이 통과 실패를 결정하게 해야하기 때문이 아래 테스트 처럼 print문을 사용하면 안 된다.
    @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();
        }
        // 참고로 이 DIscountPolicy 부분은 디테일하게 하위 요소로 잡을 수 있지만 역할과 구현을 쪼개자는 비슷한 명목이기도 하고
        // 또 DiscountPolicy라고 해두면 spring이 이를 주시하고 있기 때문에 관리측면이나 유지보수 측면에서도 훨씬 직관적이다.
        @Bean
        public DiscountPolicy fixDiscountPolicy() {
            return new FixDiscountPolicy();
        }
    }

}
profile
智(지)! 德(덕)! 體(체)!

0개의 댓글