지난번에 스프링 컨테이너와 등록된 모든 빈, 애플리케이션 빈을 확인하는 방법을 공부했다.
해당 내용은 아래 글을 참고하면 되겠다.
https://jnsodevelop.tistory.com/52
이번엔 빈 이름, 빈 타입 등으로 빈을 조회하는 방법에 대해 알아보고자 한다.
우선 기본적으로 스프링 빈을 조회하는 방법은 다음과 같다.
- ac.getBean("빈이름", 타입)
- ac.getBean(타입)
만약 조회하려는 스프링 빈이 없다면 다음과 같은 예외가 발생한다.
NoSuchBeanDefinitionException: No bean named 'xxxx' available
또한 시작하기에 앞서 AppConfig 클래스 구조가 어떻게 생겼는지 미리 보여줌으로써 변수 이름, 메소드명 등을 헷갈리지 않도록 하겠다. 다음은 AppConfig 클래스이다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService(){
return new MemberServiceImpl(memberRepository());
}
@Bean
public static MemoryMemberRepository memberRepository() {
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService(){
return new OrderServiceImpl(memberRepository(),discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy(){
return new FixDiscountPolicy();
}
}
1️⃣ 빈 이름, 타입으로 조회
class ApplicationContextBasicFindTest{
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
@Test
@DisplayName("Find by bean name")
void findBeanByName(){
MemberService memberService = ac.getBean("memberService", MemberService.class);
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
}
빈을 조회하는 방법엔 두 가지가 있다고 하였다.
- ac.getBean("빈 이름", 타입)
- ac.getBean(타입)
ac.getBean("memberService", MemberService.class) 은 빈 이름과 타입으로 조회하는 방법이다.
즉, memberService 에는 AppConfig 클래스로부터 memberService() 메소드를 호출하여 얻은 반환값인
'memberServiceImpl' 가 할당된다.
아래에 Assertions.assertThat 은 org.assertj.core.api 를 통해 불러올 수 있다.
TDD와 관련된 내용은 여기서 자세히 다루지 않겠지만, 간단하게 설명하겠다.
Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
위 코드는 memberService 가 MemberServiceImpl 의 인스턴스가 맞다면 테스트를 통과한다는 의미이다.
위에서 memberService 는 MemberServiceImpl 를 할당받았다고 하였다.
따라서 이 테스트는 통과할 수 있는 것이다.
2️⃣ 타입으로 조회
1. 인터페이스 타입
@Test
@DisplayName("Find by bean type")
void findBeanByType(){
MemberService memberService = ac.getBean(MemberService.class);
assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
}
이름과 타입으로 조회할 때와 어떤 점이 다를까?
바로 ac.getBean() 부분을 보면 된다.
MemberService memberService = ac.getBean(MemberService.class);
이렇게 이름 없이 특정 타입으로만으로도 조회가 가능하다.
여기서 중요한 점은 중복되는 타입이 없어야 한다는 것이다.
또한 MemberService.class 는 인터페이스 타입이다.
2. 구체 타입(구현체)
이번엔 인터페이스 타입이 아닌 구체 타입(구현체)로 조회하는 방법이다.
- MemberService : 인터페이스
- MemberServiceImpl : 구현체
무슨 뜻인지 알겠는가? 바로 코드로 보자.
MemberService memberService = ac.getBean(MemberServiceImpl.class);
빈을 가져올 때 MemberServiceImpl 타입을 가져온다는 뜻이다.
그러나 AppConifg 클래스의 memberService() 메소드의 반환 타입을 보면 'MemberService' 타입이다.
여기서 오류가 발생하진 않을까 생각할 수 있겠지만, 스프링 컨테이너에 등록된 인스턴스 타입을 보고 결정하기 때문에 결론적으로 문제가 되진 않는다.
만약 SOLID 원칙을 공부한 사람이라면 해당 코드는 좋은 코드가 아니라는 것을 알아챌 수 있었을 것이다.
바로 인터페이스가 아닌 구현체에 의존하였기 때문이다. (DIP 위반)
따라서 구체 타입으로 조회하는 방법보다는 인터페이스로 조회하는 것이 바람직하다.
물론 테스트는 통과한다.
3️⃣ 존재하지 않는 빈 조회시 예외 발생
테스트를 하다보면 예외가 발생하지 않는 테스트 뿐만 아니라 예외 처리에 대한 테스트가 필요할 때도 있다.
예를 들어 'TestClass' 라는 이름을 가진 클래스가 있다고 하자. 또한 이는 스프링 빈으로 등록되어 있다.
그렇다면 'TestClass' 라는 이름으로 빈을 조회하였을 때 테스트는 통과할 것이다.
반면 'TestClass2' 라는 이름으로 빈을 조회하였을 때엔 존재하지 않는 빈이므로 해당 로직은 예외가 발생할 것이다.
우리는 이러한 예외에 대한 테스트도 진행해주어야 한다.
아래 코드를 보자.
@Test
@DisplayName("Not find bean by name")
void findBeanByNameX(){
org.junit.jupiter.api.Assertions.assertThrows(NoSuchBeanDefinitionException.class,()->
ac.getBean("xxxx",MemberService.class));
}
이번엔 assertj 의 Assertion 이 아닌, 'org.junit.jpiter.api' 에 있는 Assertion 을 사용한다.
여기서 asserThrows 는 예외에 대한 테스트를 진행하는 것이라고 우선 생각하면 편하다.
참고로 해당 코드는 mac 기준 'option + enter' 을 통해 static 하게 바꿀 수 있다.
@Test
@DisplayName("Not find bean by name")
void findBeanByNameX(){
assertThrows(NoSuchBeanDefinitionException.class,()->
ac.getBean("xxxx",MemberService.class));
}
코드가 보다 간결해진 것을 볼 수 있다.
김영한님도 이러한 경우 static하게 만드는 것이 좋다고 하셨다.
다시 코드에 대한 내용으로 돌아가자.
람다식 문법이 사용되었는데, 우선 assertThrows를 통해 'NoSuchBeanDefinitionException' 예외를 받는다.
그 이유를 알기 위해 -> 이후 코드를 보면 ac.getBean("xxxx",MemberService.class) 에서 빈을 조회한다.
MemberService 타입으로 조회하지만, "xxxx" 라는 이름을 가진 빈은 존재하지 않기 때문에 위 예외가 발생한다.
따라서 정상적으로 예외가 발생했고, 테스트는 통과하게 된다.
앞으로 자주 나오는 문법이니 공부를 더 해야겠다.
참고 : 김영한-스프링 핵심 원리 (기본편)