Spring - IoC컨테이너에서 관리되고 있는 Bean 목록 조회하기
Spring에는 IoC 컨테이너에서 DI (Dependenct Injection)을 관리하는데 이때 관리되고 있는 Bean 객체를 조회하는 방법을 설명한다.
실제로 프로젝트를 할때 ApplicationContext에서 직접 Bean을 조회할 일은 흔하지 않지만 스프링 내부적으로 동작하는 부분의 이해를 목적으로 한다.
1. 스프링 컨테이너에 등록된 모든 Bean 조회
스프링 내부적으로 관리되고 있는 Bean 및 직접등록한 애플리케이션 Bean을 모두 조회하는 방법이다.
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);
}
}
}
스프링 내부적으로 관리하는 Bean의 이름은 org로 시작하는 상단 5개 : 스프링 내부에서 사용하는 Bean
직접 Configuration을 통해 등록한 Bean은 하단 5개 : 애플리케이션 Bean
2. 직접 등록한 애플리케이션 Bean 조회
개발자가 직접 @Configuration, @Bean 어노테이션을 통해 직접 등록한 Bean을 조회한다.
public class ApplicationContextInfoTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("애플리케이션 빈 출력하기")
void findApplicationBean() {
String[] beanDefinitionNames = ac.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
//Role ROLE_APPLICATION: 직접 등록한 애플리케이션 빈
//Role ROLE_INFRASTRUCTURE: 스프링이 내부에서 사용하는 빈
if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
Object bean = ac.getBean(beanDefinitionName);
System.out.println("name = " + beanDefinitionName + ", object = " + bean);
}
}
}
}
위 처럼 작성하게되면 스프링 내부적으로 관리하고 있는 Bean을 제외한 직접 애플리케이션에서 설정한 Bean의 목록을 조회할 수 있다.
3. Bean 이름으로 조회
Bean의 Name으로 조회한다.
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);
}
}
4. Bean 타입으로 조회
Bean의 Type으로 조회한다.
import static org.assertj.core.api.Assertions.*;
public class ApplicationContextBasicFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("빈 이름 없이 타입으로 조회")
void findBeanByType() {
MemberService memberService = ac.getBean(MemberService.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
}
💡 동일한 타입이 둘 이상인 Bean 조회
타입으로 조회시 여러 개의 Bean이 나올 경우 NoUniqueBeanDefinitionException이 발생한다.
타입이 둘 이상인 경우에는 아래와 같이 빈 이름을 지정해주어야 한다.
※ 위 테스트에서 사용한 AppConfig Class를 사용하지 않고 내부 클래스로 테스트 환경을 만들었음
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class ApplicationContextSameBeanFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);
@Test
@DisplayName("타입으로 조회 시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다")
void findBeanByTypeDuplicate() {
assertThrows(NoUniqueBeanDefinitionException.class, () -> ac.getBean(MemberRepository.class));
}
@Test
@DisplayName("타입으로 조회 시 같은 타입이 둘 이상 있으면, 빈 이름을 지정하면 된다")
void findBeanByName() {
MemberRepository memberRepository = ac.getBean("memberRepository1", MemberRepository.class);
assertThat(memberRepository).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();
}
}
}
💡 상속관계에서 Bean 조회하기
타입으로 조회시 부모 타입으로 조회 할 때에 자식이 둘 이상 있으면 오류가 발생하게 된다.
상속 관계를 이해하고 테스트 해보자.
rateDiscountPolicy, fixDiscountPolicy는 모두 DiscountPolicy 인터페이스의 구현체이다.
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class ApplicationContextExtendsFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
@Test
@DisplayName("부모 타입으로 조회 시, 자식이 둘 이상 있으면 중복 오류가 발생한다")
void findBeanByParentTypeDuplicate() {
// NoUniqueBeanDefinitionException
assertThrows(NoUniqueBeanDefinitionException.class, () -> ac.getBean(DiscountPolicy.class));
}
@Test
@DisplayName("부모 타입으로 조회 시, 자식이 둘 이상 있으면 빈 이름을 지정하면 된다")
void findBeanByParentTypeBeanName() {
DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);
}
@Test
@DisplayName("특정 하위 타입으로 조회")
void findBeanBySubType() {
RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
}
@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();
}
@Bean
public DiscountPolicy fixDiscountPolicy() {
return new FixDiscountPolicy();
}
}
}
💡 실패 테스트 (assertThrows)
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;
public class ApplicationContextBasicFindTest {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("빈 이름으로 조회 실패")
void findBeanByNameX() {
// NoSuchBeanDefinitionException 터지면 성공
assertThrows(NoSuchBeanDefinitionException.class, () -> ac.getBean("xxxxx", MemberService.class));
}
}
'Programming > Spring' 카테고리의 다른 글
[Spring] Lombok 생성자 @NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor 차이점 (0) | 2023.06.17 |
---|---|
[Spring] @Configuration, @Bean의 동작 방식(싱글톤) (0) | 2023.05.23 |
[Spring] JPA 개념정리 (Hibernate, Spring Data JPA) (0) | 2023.05.08 |
DTO, VO, Entity - 개념 정리 및 차이점 (0) | 2023.03.15 |
[Spring] WebFlux란 무엇인가? - 개념(특징), MVC와 비교, 사용 이유 (0) | 2022.12.26 |