참고
Spring core basic 시리즈는 김영한 님의 "스프링 핵심 원리 - 기본편" 강의를 정리한 글입니다. 글에 첨부된 사진은 해당 강의의 강의 자료에서 캡쳐한 것입니다. 제 Github에만 올려뒀다가, 정보 공유와 강의 홍보(?)를 위해 블로그에도 업로드합니다. 마크다운을 잘 쓰지 못해서 가독성이 조금 떨어지는 점 양해 바랍니다.
스프링 핵심 원리 - 기본편 - 인프런 | 강의
스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., - 강의 소개 | 인프런...
www.inflearn.com
스프링 빈 조회
가장 기본적인 스프링 빈 조회 방법
ac.getBean("빈 이름", 타입)
ac.getBean(타입)
조회 대상 스프링 빈이 없으면 예외가 발생한다.NoSuchBeanDefinitionException: No bean named '빈 이름' available
ApplicationContextBasicFindTest.java
package com.kloong.corebasic1.findbean;
//import 생략
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 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 findBeanByNameFailure() {
assertThrows(NoSuchBeanDefinitionException.class,
() -> ac.getBean("nobean", MemberService.class));
}
}
스프링 빈 조회시 동일한 타입의 빈이 여러개일 경우
ac.getBean()
메소드를 사용해서 타입으로 조회를 할 경우, 같은 타입의 스프링 빈이 둘 이상이면 오류가 발생한다. 이 때는 빈 이름을 지정하여서 조회를 하면 된다.
아니면 ac.getBeansOfType()
메소드를 사용해서 해당 타입의 모든 빈을 조회할 수 있다.
ApplicationContextSameTypeBeanFindTest.java
package com.kloong.corebasic1.findbean;
import com.kloong.corebasic1.AppConfig;
import com.kloong.corebasic1.discount.DiscountPolicy;
import com.kloong.corebasic1.member.MemberRepository;
import com.kloong.corebasic1.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;
public class ApplicationContextSameTypeBeanFindTest {
//AppConfig.class 아니고 SameBeanAppConfig.class를 넘겨줬다.
AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext(SameBeanAppConfig.class);
@Test
@DisplayName("타입으로 조회 시 같은 타입이 둘 이상 있으면 오류가 발생한다")
void findBeanByDuplicateType() {
Assertions.assertThrows(NoUniqueBeanDefinitionException.class,
() -> ac.getBean(MemberRepository.class));
}
//테스트를 위해 AppConfig를 수정하는 일이 발생하지 않도록
//테스트용 내부 클래스를 새롭게 작성했음.
@Configuration
static class SameBeanAppConfig {
@Bean
public MemberRepository memberRepository1() {
return new MemoryMemberRepository();
}
@Bean
public MemberRepository memberRepository2() {
return new MemoryMemberRepository();
}
}
}
MemberRepository 타입의 빈이 2개 존재하므로 ac.getBean(MemberRepository.class)
를 실행하면 NoUniqueBeanDefinitionException
이 발생한다.
NoUniqueBeanDefinitionException 메세지
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.kloong.corebasic1.member.MemberRepository' available: expected single matching bean but found 2: memberRepository1,memberRepository2
MemberRepository 타입의 빈이 "memberRepository1"과 "memberRepository2" 이렇게 두개가 존재한다고 한다.
*참고: 실제로 같은 타입의 빈을 여러개 등록하는 경우가 생길 수 있다. 필요에 따라 객체 생성자의 파라미터값을 다르게 해서 같은 타입의 빈을 여러 개 등록하는 경우가 있다.
특정 타입의 모든 빈 조회
ApplicationContextSameTypeBeanFindTest.java
package com.kloong.corebasic1.findbean;
//import 생략
public class ApplicationContextSameTypeBeanFindTest {
AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext(SameBeanAppConfig.class);
@Test
@DisplayName("특정 타입을 모두 조회하기")
void findAllBeansByType() {
//Map 형태로 반환된다.
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);
//MemberRepository 타입의 빈이 2개 존재한다
assertThat(beansOfType.size()).isEqualTo(2);
}
@Configuration
static class SameBeanAppConfig {
@Bean
public MemberRepository memberRepository1() {
return new MemoryMemberRepository();
}
@Bean
public MemberRepository memberRepository2() {
return new MemoryMemberRepository();
}
}
}
굳이 특정 타입의 모든 빈을 조회할 일이 있을까? 라는 생각이 들지만, 내부적으로 이 기능이 활용된다고 한다. 나중에 배운다고 함.
상속 관계를 통한 스프링 빈 조회
여러 스프링 빈의 타입 중 상속 관계에 있는 타입이 있다고 하자. 이런 경우 부모 타입으로 스프링 빈을 조회하면, 자식 타입의 스프링 빈도 함께 조회된다.
따라서 Object
타입으로 조회하면 모든 스프링 빈을 조회할 수 있다.
ApplicationContextFindExtendTypeTest.java
package com.kloong.corebasic1.findbean;
//import 생략
public class ApplicationContextFindExtendTypeTest {
//내부 클래스를 넘겨줘서 컨테이너를 생성한다.
AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext(ExtendTypeAppConfig.class);
@Test
@DisplayName("부모 타입으로 조회 시, 자식이 둘 이상 있으면 오류 발생")
void findBeansByParentType() {
assertThrows(NoUniqueBeanDefinitionException.class,
() -> ac.getBean(DiscountPolicy.class));
}
@Test
@DisplayName("부모 타입으로 조회 시, 자식이 둘 이상 있으면 빈 이름을 지정해서 조회하면 된다")
void findBeansByParentTypeAndBeanName() {
DiscountPolicy discountPolicy =
ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
assertThat(discountPolicy).isInstanceOf(RateDiscountPolicy.class);
}
//구현체에 의존하는 것은 좋지 않다.
@Test
@DisplayName("특정 하위 타입으로 조회")
void findBeanBySubType() {
RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
}
@Test
@DisplayName("부모 타입으로 모두 조회")
void findAllBeansByParentType() {
Map<String, DiscountPolicy> beansOfType =
ac.getBeansOfType(DiscountPolicy.class);
for (String key : beansOfType.keySet()) {
System.out.println(
"key = " + key + " value = " + beansOfType.get(key));
}
assertThat(beansOfType.size()).isEqualTo(2);
}
@Test
@DisplayName("Object 타입으로 모두 조회 -> 모든 빈이 조회된다.")
void findAllBeansBytObjectType() {
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 ExtendTypeAppConfig {
@Bean
public DiscountPolicy rateDiscountPolicy() {
return new RateDiscountPolicy();
}
@Bean
public DiscountPolicy fixDiscountPolicy() {
return new FixDiscountPolicy();
}
}
}
실제로는 빈 조회를 직접 할 일은 그리 많지 않다고 한다. 실무 개발을 한다고 하면, 클라이언트 코드의 실제 로직만 개발하고 의존관계에 대한 코드(AppConfig.java)는 건드릴 일이 많지 않다.
간혹 순수 Java 코드에 스프링 프레임워크를 적용하는 경우가 있는데 그런 경우에 빈 조회를 하면 된다.
'Spring > Spring core basic' 카테고리의 다른 글
[Spring core basic] 21 - Spring의 다양한 설정 방식 (0) | 2022.05.02 |
---|---|
[Spring core basic] 20 - BeanFactory와 ApplicationContext (0) | 2022.05.02 |
[Spring core basic] 18 - 컨테이너에 등록된 모든 빈 조회 (0) | 2022.05.02 |
[Spring core basic] 17 - 스프링 컨테이너 생성 (0) | 2022.05.02 |
[Spring core basic] 16 - 스프링으로 전환하기 (0) | 2022.05.02 |
댓글