스프링/SpringBasicCore

Section4(1편) 스프링 컨테이너와 스프링 빈

나는웅쓰 2024. 2. 7. 15:55
인프런 강의 中 
김영한 강사님의 '스프링 핵심 원리' 내용을 정리했습니다.

Section3까지는 OCP, DIP를 지키는 코드를 순수 자바 코드로도 작성해보고 스프링을 사용한 코드로 리팩토링도 해보았다. 이때 우리는 스프링 컨테이너라는 곳에 의존성 주입을 시킨 Bean을 등록하고 사용했었는데, Section4에서는  Bean이 컨테이너에 어떤 형태로 등록되고 원하는 빈을 어떻게 찾을 수 있는지 김영한 강사님의 강의를 토대로 학습해 보겠다.

 

스프링 컨테이너 생성

ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

이전 시간에 사용했던 스프링 컨테이너 생성 코드

스프링 컨테이너 생성 과정

우리는 위의 도식화된 이미지와 같은 과정을 통해 스프링 컨테이너를 생성한다. 우리가 작성한 코드를 대입해보면 AnnotationConfigApplicationContext 클래스를 통해서 스프링 컨테이너를 생성했고, 구성 정보로 활용되는 AppConfig.class를 파라미터로 주입시켰다. 스프링 컨테이너를 생성하기 위해 GenericXmlApplicationContext와 같은 다른 클래스를 사용해도 된다. 이에 대한 내용은 뒷 부분에서 자세히 설명해주신다.

 

스프링 빈(Bean) 등록

스프링 컨테이너에 Bean이 등록된 모습

스프링 컨테이너 생성시 파라미터로 주입된 파일의 내용을 바탕으로 Bean이 저장된다.

이 때, Bean 이름은 메서드 이름으로 자동 지정된다. 그리고 직접 부여할 수 도 있다.ex)@Bean(name="ungs")

주의!! : 빈 이름은 항상 다른 이름을 부여해야 한다. 같은 이름을 부여하면, 다른 빈이 무시되거나, 기존 빈을 덮어버리거나 설정에 따라 오류가 발생한다.

Bean 등록시 의존 관계 설정

의존성 주입은 메서드에서 리턴되는 값을 토대로 설정된다.

 

컨테이너에 등록된 Bean 조회

등록된 Bean을 조회하는 방법은 여러가지가 있다. 그 중 스프링 컨테이너에서 스프링 빈을 찾는 가장 기본적인 조회 방법은 다음과 같다.

  • ac.getBean(빈이름, 타입)
  • ac.getBean(타입)
  • 조회 대상 스프링 빈이 없으면 예외 발생
    • NoSuchBeanDefinitionException: No bean named 'xxxxxx' available

 

다양한 방법들을 Junit을 사용해 테스트 코드로 작성해서 결과값을 살펴보겠다. 

컨테이너 생성 및 빈 등록

AppConfig 파일을 토대로 컨테이너에 빈 등록

 

모든 Bean 조회하기

모든 Bean을 출력하는 테스트 코드

getBeanDefinitionNames() 메소드를 사용하면 스프링이 내부적으로 사용하는 빈과 AppConfig 파일로 등록한 빈들 모두 출력된다.

 

  ac.getBeanDefinitionNames() : 스프링에 등록된 모든 Bean 이름을 조회

  ac.getBean() : Bean 이름으로 Bean 객체(인스턴스)를 조회

 

애플리케이션 Bean 조회하기

직접 등록한 Bean만 출력하는 코드
등록된 Bean 출력 결과

BeanDefinition.ROLE_APPLICATION: 직접 등록한 애플리케이션 빈

위 코드를 사용해 모든 Bean 중 직접 등록한 애플리케이션 Bean 역할을 하는 Bean만 출력했다. 결과 값을 살펴보면 AppConfig.java 파일에서 등록한 모든 Bean들이 모두 출력되는 모습을 확인할 수 있다.(AppConfig.java 파일은 Section3에서 등록했으니 기억이 안나면 Section3 글을 확인하자!)

 

★ ROLE_APPLICATION: 일반적으로 사용자가 정의한 빈

★ ROLE_INFRASTRUCTURE: 스프링이 내부에서 사용하는 빈

 

Bean 이름으로 조회하기

Bean 이름으로 조회하는 코드
Bean 이름으로 조회한 결과

위 코드를 실행하면 memberService라는 이름으로 등록된 Bean을 조회할 수 있다.

 

구체 타입으로 조회

구체 타입으로 Bean 조회

Bean 이름으로 조회하는 코드와 다른 점은 타입 지정이다. 구체 타입으로 Bean을 조회할 때는 역할에 의존하는 것이 아닌 구현체에 의존하게 된다. 그래서 만약 AppConfig 파일에서 다른 구현체에 의존하게 된다면 위 코드의 타입도 수정해야하기 때문에 유연성이 떨어진다.

 

이름 없이 타입으로만 조회하기

타입으로만 Bean 조회하기

MemberService를 type으로 가지고 있는 bean은 memberService다.

memberService bean은 MemberServiceImpl 클래스를 리턴 값으로 지정해놨기 때문에 findBeanByType() 메소드의 지역 변수인 memberService에는 MemberServiceImpl 인스턴스가 담기게 된다.

그래서 isInstanceOf() 메소드를 사용해 MemberServiceImpl과 비교해보면 같다는 결과를 받을 수 있게 된다. 

 

빈 이름 조회 실패 테스트

조회 실패 테스트 코드

없는 빈 이름을 조회하게 되면 NoSuchBeanDefinitionException error가 발생한다. 이 점을 활용해 해당 에러가 발생하면 테스트가 성공하는 코드를 작성했다. 참고로 위 코드의 assertThrows는

org.junit.jupiter.api.Assertions.assertThrows

를 static import 한 것이다. 평소에 사용하는 org.assertj.core.api.Assertions와는 다른 함수이기 때문에 주의!!