반응형

 

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

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;

public class ApplicationContextInfoTest {
    //스프링 컨테이너 불러옴
    //구성 정보는 AppConfig에 있다.
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    @Test
    @DisplayName("모든 빈 출력하기")
    void findAllBean(){
        //스프링 빈 이름들을 String으로 빼냄
        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으로 빼냄
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            //BeanDefinition => 빈에대한 정보들, 정보들 가져옴
            BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
            //role_appliction과 role_infrastructure(내부에서 사용하는 빈)가 있음
            //역할로 걸러서 출력함
            if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
                Object bean = ac.getBean(beanDefinitionName);
                System.out.println("name = " + beanDefinitionName + " Object = " + bean);
            }

        }
    }

}

 

 

스프링 빈 조회 - 기본

스프링 컨테이너에서 스프링 빈을 찾는 가장 기본적인 조회방법은

ac.getBean(빈이름, 타입)

ac.getBean(타입) => 이름 생략가능

 

만약 조회 대상이 없을 경우엔 예외가 발생한다.

NoSuchBeanDefinitionException: No bean named "xxxxx" available

package hello.core.beanfind;

import hello.core.AppConfig;
import hello.core.member.MemberService;
import hello.core.member.MemberServiceImpl;
import org.assertj.core.api.Assertions;
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 static org.assertj.core.api.Assertions.*;

import static org.junit.jupiter.api.Assertions.assertThrows;

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);
        //isInstanceOf => 어떤 클래스인가?
    }

    @Test
    @DisplayName("이름 없이 빈 타입으로 조회")
    void findBeanByType(){
        //인터페이스로 조회하면 알아서 사용하는 구현체(스프링 빈에 등록된)가 조회된다.
        MemberService memberService = ac.getBean(MemberService.class);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
        //isInstanceOf => 어떤 클래스인가?
    }

    @Test
    @DisplayName("구체 타입으로 조회")
    void findByName2(){
        //구현체에 의존하게 된다.....
        MemberService memberService = ac.getBean(MemberServiceImpl.class);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
        //isInstanceOf => 어떤 클래스인가?
    }

    //조회가 안되는 경우도 테스트 해봐야해!!
    @Test
    @DisplayName("빈 이름으로 조회X")
    void findByNameX(){
        //MemberService memberService = ac.getBean("XXX", MemberService.class);
        //조회가 안되면 NoSuchBeanDefinitionException 터친다.
        //Junit Assertion의 assertThrows를 사용해서 예외를 확인 해야한다.
        assertThrows(NoSuchBeanDefinitionException.class, ()->ac.getBean("XXX", MemberService.class));

    }


}

 

**참고 assertThrows

org.junit.jupiter.api.Assertions.assertThrows(

 NoSuchBeanDefinitionException.class, // 발생이 예상되는 예외의 타입

 ()-> ac.getBean("xxxx", MemberService.class) // 예외가 발생될 수 있는 코드 블록

);


/*
.

먼저 해당 테스트 메소드는 존재하지 않는 빈의 이름으로 빈을 가져오려고 할 때 예외가 발생되는 상황을 테스트 하기 위한 메소드입니다.

ac.getBean("xxxx", MemberService.class); 해당 문장을 실행하면 존재하지 않는 빈의 이름(xxxx)으로 빈을 꺼내오려고 할 것입니다. 그러나 당연히 xxxx라는 이름으로 등록된 빈이 없기 때문에 NoSuchBeanDefinitionException 예외가 발생합니다.

.

assertThrows 메소드는 발생이 예상되는 예외의 타입, 예외가 발생될 수 있는 코드 블록을 파라미터로 받아서 실행됩니다.

이 때, assertThrows 내부에서는 예외가 발생될 수 있는 코드 블록을 실행합니다. 만약 해당 코드 블록을 실행 중 예외가 발생한다면 발생된 예외가 발생이 예상되는 예외의 타입과 일치하는지 아닌지 확인합니다. 이때 발생된 예외 타입과 예상되는 예외의 타입이 일치하면 테스트는 성공으로 처리됩니다.

.

아래 코드는 assertThrows의 내부 동작입니다. try 블록 안에서 코드를 실행하여 예외 발생시 catch 내에서 발생한 예외 타입과 예상되는 예외 타입을 비교하고 있습니다.
*/

 

 

스프링 빈 조회 - 동일한 타입이 둘 이상

타입으로 조회시 같은 타입의 스프링 빈이 둘 이상이면 오류 발생!!!!

이때는 빈 이름을 지정하자

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

 

package hello.core.beanfind;

import hello.core.discount.DiscountPolicy;
import hello.core.member.MemberRepository;
import hello.core.member.MemoryMemberRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

//타입 으로 조회시 같은 타입이 둘 이상이면...
// 테스트를 위해 간단한 설정 클래스 만듬
public class ApplicationContextSameBeanFindTest {
    //테스트를 위한 sameBean 설정 정보를 가져옴
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);


    @Test
    @DisplayName("타입으로 조회시 같은 타입이 둘 이상이면 중복오류 발생")
    void findBeanTypeDuplicate(){
        //먼저 어떤 예외를 던지는지 확인해본다.
        //MemberRepository bean = ac.getBean(MemberRepository.class);
        /*
        org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'hello.core.member.MemberRepository' available: expected single matching bean but found 2: memberRepository1,memberRepository2
        NoUniqueBeanDefinitionException를 던진다.
         */
        Assertions.assertThrows(NoUniqueBeanDefinitionException.class, ()->ac.getBean(MemberRepository.class));

    }

    @Test
    @DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 빈 이름을 지정하면 된다.")
    void findBeanByName(){
        //설정클래스에 메소드 둘다 MemberRepository이다.따라서 이름을 같이 지정
        MemberRepository bean = ac.getBean("memberRepository1", MemberRepository.class);
        org.assertj.core.api.Assertions.assertThat(bean).isInstanceOf(MemberRepository.class);

    }

    //특정 타입 모두 조회 => 둘 다 꺼내고 싶음
    //getBeansOfType 사용하면 된다.
    //MemberRepository형 모두 꺼냄 
    @Test
    @DisplayName("특정 타입을 모두 조회하기")
    void findAllBeanType(){
        //getBeansOfType은 Map형으로 반환함 <이름, 타입>
        Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
        for (String key : beansOfType.keySet()) {
            System.out.println("key = " + key);            
        }
        System.out.println("beansOfType = " + beansOfType);
        org.assertj.core.api.Assertions.assertThat(beansOfType.size()).isEqualTo(2);
        //해당 타입의 개수가 2개이므로 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 org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;

import java.util.Map;


public class ApplicationContextExtendsFindTest {
    //스프링 컨테이너
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

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

    }

    @Test
    @DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 빈 이름 지정해서 쓰면댐")
    void findBeanByParentTypeBeanName(){
        DiscountPolicy bean = ac.getBean("fixDiscountPolicy", DiscountPolicy.class);
        org.assertj.core.api.Assertions.assertThat(bean).isInstanceOf(FixDiscountPolicy.class);

    }


    @Test
    @DisplayName("특정 하위 타입으로 조회")
    void findBeansBySubType(){
        RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
        org.assertj.core.api.Assertions.assertThat(bean).isInstanceOf(RateDiscountPolicy.class);

    }


    @Test
    @DisplayName("부모 타입으로 다 조회")
    void findBeansByParentType1(){
        Map<String, DiscountPolicy> beansOfType = ac.getBeansOfType(DiscountPolicy.class);
        for (String key : beansOfType.keySet()) {
            System.out.println("key + "+ key + "value = "+ beansOfType.get(key));


        }

    }

    @Test
    @DisplayName("부모 타입으로 다 조회-object")
    void findBeansByParentType2(){
        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();
        }
    }
}

 

 

직접 getBean할 경우는 잘 없음!!

개발하면서 애플리케이션 컨텍스트에서 빈을 조회할 일이 거의 없음.

하지만 기본기능이고, 가끔 순수 자바애플리케이션에서 스프링컨테이너를 생성해서 쓸때 사용한다.

 


 

BeanFactory와 ApplicationContext

ApplicationContext, AnnotationConfig(구현클래스) ---> ApplicationContext(인터페이스) ---> BeanFactory(인터페이스)

 

BeanFactory

스프링 컨테이너의 최상위 인터페이스.

스프링 빈을 관리, 조회하는 역할 담당, 예시로 getBean을 제공해줌

이전 코드들에서 대부분 사용한 기능은 BeanFactory가 제공하는 기능임,

Then, 왜 BeanFactory가 아닌 ApplicationContext를 사용했을까??

 

ApplicationContext

BeanFactory 기능을 모두 상속받아서 제공한다.

차이점은, 애플리케이션 개발시, 빈은 관리하고 조회하는 기능이외에 수많은 기능들이 필요하다.

 

예를 들면 ApplicationContext가 구현하는 인터페이스들을 보면

MessageSource = 메시지 소스를 활용한 국제화기능(한국에서 들어오면 한국어, 외국에서 들어오면 영어)

EnvironmnetCapable = 환경변수이다. 로컬, 개발, 운영등 구분해서 처리 (예를 들면 로컬 개발환경, 테스트서버 개발환경, 실제운영환경 등등 사용하는 DB가 다르다면? 맞게 설정)

ApplicationEventPublisher = 애플리케이션 이벤트이다. 이벤트를 발행하고 구독하는 모델을 편리하게 지원

ResourceLoader = 편리한 리소스 조회. 파일, 클래스패스,외부 등에서 리소스를 편리하게 조회

 

일단 이런게 있다 정도만 알고 가자...

 

정리하면 ApplicationContext는 BeanFactory 기능들에 편리한 기능을 추가한것이다. 따라서 BeanFactory는 거의 잘 안쓴다.

 

728x90
반응형
블로그 이미지

아상관없어

,