-
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(); }
위 코드에서 첫번째 코드처럼 빈을 등록하는것과 두번째 코드처럼 빈을 등록하는 것이 동일하지만 첫번째 코드가 권장되는 이유 개발에 대한 설계를 할때 역할과 구현을 쪼개는 것이 이상적이기 때문이다. 즉 추상(인터페이스)으로 타입을 사용하는 것이 역할과 구현을 구분하게 해준다.
[출처 - 스프링 핵심 원리 - 기본편]
'FrameWork > Spring&Spring-boot' 카테고리의 다른 글
Spring 기본 13 - 스프링 빈 설정 메타 정보 - BeanDefinition (0) 2023.11.10 Spring 기본 12 - BeanFactory와 ApplicationContext (0) 2023.11.10 Spring 기본 10 - 스프링 컨테이너와 스프링 (0) 2023.11.10 Spring 기본 9 - 스프링으로 전환하기 (0) 2023.11.09 Spring 기본 8 - IoC, DI 그리고 컨테이너 (1) 2023.11.09 - 모든 빈 출력하기