ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring 기본 11 - 스프링 빈 조회
    FrameWork/Spring&Spring-boot 2023. 11. 10. 13:09

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

    스프링 컨테이너에 실제 스프링 빈들이 잘 등록 되었는지 확인해보자.

    package com.hello.core.beanfind;
    
    import com.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 ApplicationContectInfoTest {
    
        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) {
                //각각 bean에 대한 메타데이터 정보
                BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
                if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION){
                    Object bean = ac.getBean(beanDefinitionName);
                    System.out.println("name = " + beanDefinitionName + "object = "+ bean);
                }
            }
        }
    
    }

     

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

     

    스프링 빈 조회 - 기본

     

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

    • ac.getBean(빈이름, 타입)
    • ac.getBean(타입)
    • 조회 대상 스프링 빈이 없으면 예외 발생
      • NotSuchBeanDefinitionException: No bean named 'xxxxxx' available
    package com.hello.core.beanfind;
    
    import com.hello.core.AppConfig;
    import com.hello.core.member.MemberService;
    import com.hello.core.member.MemberServiceImpl;
    import org.junit.jupiter.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;
    
    public class ApplicaiotnContextBasicFindTest {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
    
        @Test
        @DisplayName("빈 이름으로 조회")
        void findBeanByName(){
            MemberService memberService = ac.getBean("memberService",MemberService.class);
            Assertions.assertInstanceOf(MemberService.class,memberService);
        }
    
        @Test
        @DisplayName("이름 없이 타입으로만 조회")
        void findBeanByType(){
            MemberService memberService = ac.getBean(MemberService.class);
            Assertions.assertInstanceOf(MemberService.class,memberService);
        }
    
        @Test
        @DisplayName("구체 타입으로 조회")
        void findBeanByName2(){
            MemberServiceImpl memberService = ac.getBean("memberService", MemberServiceImpl.class);
            Assertions.assertInstanceOf(MemberService.class,memberService);
        }
    
        @Test
        @DisplayName("빈 이름으로 조회X")
        void findBeanByNameX(){
            //ac.getBean("xxxx", MemberService.class);
            Assertions.assertThrows(NoSuchBeanDefinitionException.class,
                    () -> ac.getBean("xxxx",MemberService.class));
        }
    }

     

    테스트 이름 "구체 타입으로 조회"에서 처럼 MemberSerivce가 아닌 @Bean으로 메서드를 등록할시에 반환하도록 설정했던 구체 타입으로 객체 인스턴스를 조회해도 제대로 객체 인스턴스가 조회된다. 하지만 되도록 이 방법은 지양하는 것이 좋다. 왜냐하면 역활과 구현을 구분하고 역활에만 의존하는 것이 더 좋은 프로그래밍 방법이기 때문이다.

     

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

    • 타입으로 조회시 같은 타입의 스프링 빈이 둘 이상이면 오류가 발생한다. 이때는 빈 이름을 지정하자
    • ac.getBeansOfType()을 사용하면 해당 차입의 모든 빈을 조회할 수 있다.
    package com.hello.core.beanfind;
    
    import com.hello.core.member.MemberRepository;
    import com.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.beans.factory.annotation.Configurable;
    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 {
    
    AnnotationConfigApplicationContext ac =
    	new AnnotationConfigApplicationContext(SameBeanClassConfig.class);
    
        @Test
        @DisplayName("스프링 컨테이너에 둘 이상의 객체 인스턴스가 있을 경우 type으로 조회시, 중복 오류가 발생")
        void findBeanByTypeDuplicate(){
            //MemberRepository bean = ac.getBean(MemberRepository.class);
            //NoUniqueBeanDefinitionException
    
            Assertions.assertThrows(NoUniqueBeanDefinitionException.class,
                    () -> ac.getBean(MemberRepository.class));
    
        }
    
        @Test
        @DisplayName("스프링 컨테이너에 두 이상의 객체 인스턴스가 있을 경우,빈이름을 지정하면 된다.")
        void findBeanByName(){
            MemberRepository memberRepository = ac.getBean("memberRepository", MemberRepository.class);
            Assertions.assertInstanceOf(MemberRepository.class,memberRepository);
        }
    
        @Test
        @DisplayName("특정 타입을 모두 조회하기")
        void findAllBeanByType(){
            Map<String, MemberRepository> beansOfType = ac.getBeansOfType(MemberRepository.class);
    
            for (String key : beansOfType.keySet()) {
                System.out.println("key = " + key +" vlaue = "+ beansOfType.get(key));
            }
            
            System.out.println("beansOfType = " + beansOfType);
            Assertions.assertEquals(beansOfType.size(),2);
        }
    
        @Configuration
        static class SameBeanClassConfig {
    
            @Bean
            public MemberRepository memberRepository() {
                return new MemoryMemberRepository();
            }
    
            @Bean
            public MemberRepository memberRepository2() {
                return new MemoryMemberRepository();
            }
        }
    
    }

     

    클래스 안의 클래스

     @Configuration
        static class SameBeanClassConfig{
    
        }

    클래스 안에서 또 클래스를 정의하때는 static을 붙여서 static이 붙은 클래스를 해당클래스의 상위 클래스에서만 사용하겠다는 의미이다.

     

    같은 객체 인스턴스를 반환하는 @Bean을 두번 등록

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

     

    위 코드와 같이 같은 타입의 타입을 반환하는 객체 인스턴스를  @Bean을 두번 등록하는 이유는 생성자의 매개변수로 들어가는 값이 다를 수도 있기 때문이다. 성능에 따라 한번에 member를 10개 생성하는 경우와 member를 1000개 생성하는 경우로 다르게 사용되는 경우도 충분히 존재할 수 있기 때문이다.

     

    스프링 빈 조회 - 상속관계

    • 부모 타입으로 조회하면, 자식 타입도 함께 조회한다. (대 원칙)
    • 그래서 모든 자바 객체의 최고 부모인 Object 타입으로 조회하면, 모든 스프링 빈을 조회한다.
    package com.hello.core.beanfind;
    
    import com.hello.core.discount.DiscountPolicy;
    import com.hello.core.discount.FixDiscountPolicy;
    import com.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.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 ApplicationcontextExtendsFindTest {
    
        AnnotationConfigApplicationContext ac =
                new AnnotationConfigApplicationContext(TestConfig.class);
    
        @Test
        @DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면 중복 오류가 발생한다.")
        void findBeanByParentTypeDuplicate(){
            //NoUniqueBeanDefinitionException 오류 발생
            //DiscountPolicy bean = ac.getBean(DiscountPolicy.class);
    
            Assertions.assertThrows(NoUniqueBeanDefinitionException.class,
                    ()-> ac.getBean(DiscountPolicy.class));
        }
    
        @Test
        @DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면 빈이름을 지정한다.")
        void findBeanByParentTypeByBeanName(){
            DiscountPolicy rateDiscountPolicy = 
            ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
            Assertions.assertEquals(rateDiscountPolicy,RateDiscountPolicy.class);
        }
    
        @Test
        @DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면 특정 하위 타입으로 조회한다.")
        void findBeanBySubType(){
            RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
            Assertions.assertEquals(bean,RateDiscountPolicy.class);
        }
    
        @Test
        @DisplayName("부모 타입으로 하위의 모든 자식 객체 인스턴스 조회")
        void findAllBeanParentType(){
             Map<String, DiscountPolicy> beansOfType = ac.getBeansOfType(DiscountPolicy.class);
             Assertions.assertEquals(beansOfType.size(),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();
            }
        }
    }

     

     

    Bean 등록 시 추상(인터페이스)타입으로 역할과 구현 분리

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

     

    위 코드에서 첫번째 코드처럼 빈을 등록하는것과 두번째 코드처럼 빈을 등록하는 것이 동일하지만 첫번째 코드가 권장되는 이유 개발에 대한 설계를 할때 역할과 구현을 쪼개는 것이 이상적이기 때문이다. 즉 추상(인터페이스)으로 타입을 사용하는 것이 역할과 구현을 구분하게 해준다.

     

     

     

     

    [출처 - 스프링 핵심 원리 - 기본편]

    https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

     

    스프링 핵심 원리 - 기본편 - 인프런 | 강의

    스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보세요! 📢

    www.inflearn.com

     

    댓글

Designed by Tistory.