[스프링 핵심 원리] 스프링 컨테이너와 빈

JUJU·2024년 2월 15일

Spring

목록 보기
4/21
본 포스트는 김영한 개발자님의 스프링 핵심 원리 강의를 듣고 정리한 것입니다.
※ 코드는 강의에서 사용된 것과 다릅니다.
jaewon-ju Github Address

✏️ 스프링 컨테이너

스프링 컨테이너란, 자바 객체(빈)의 생명 주기를 관리하는 스프링 컴포넌트이다.

스프링 컨테이너는 빈의 관리/검색 및 부가기능을 제공한다.

  • 스프링 컨테이너의 종류는 BeanFactory와 ApplicationContext가 있다.
  • ApplicationContext는 BeanFactory를 상속한다.
  • ApplicationContext, BeanFactory는 모두 인터페이스이다.
  • 빈의 관리 및 검색은 BeanFactory 인터페이스에서 담당하고, 부가기능은 ApplicationContext에서 담당한다.

■ 스프링 컨테이너 생성

ApplicationContext applicationContext = new AnnotationConfigApplication(AppConfig.class)

ApplicationContext를 스프링 컨테이너라 한다.
위의 코드는, AppConfig.class를 설정 정보로 하여 스프링 컨테이너를 만든 것이다.

  • 스프링 컨테이너에는 "빈 저장소"가 존재한다.
  • 설정 정보를 기반으로, 빈 이름과 빈 객체를 저장소에 등록한다.
  • 빈 이름은 메서드 이름을 사용한다. (중복 불가)

■ 컨테이너 생성 과정

  1. 스프링 컨테이너 생성
  2. 스프링 컨테이너 내부의 빈 저장소에 빈 이름과 객체 등록
  3. 설정 정보를 기반으로 빈 간의 의존관계 설정
    (생성자로 의존관계 주입을 하는 경우, 2와 3이 동시에 발생할 수 있다.)



✏️ 빈 조회하기

■ 기본 조회

기본 설정 정보 AppConfig 클래스

@Configuration
public class AppConfig {

    @Bean
    public MemberRepository memberRepository(){
        return new MemoryMemberRepository();
    }
    @Bean
    public MemberService memberService(){
        return new MemberServiceImpl(memberRepository());
    }
    @Bean
    public BoardPolicy boardPolicy(){ 
    	return new ReadOnly(); 
    }
    @Bean
    public BoardService boardService(){
        return new BoardServiceImpl(memberRepository(), boardPolicy());
    }
}

스프링 컨테이너 생성

public class GetBeanTest {
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
	// 스프링 컨테이너 생성
  1. 빈 이름으로 조회하기
    ac.getBean(이름)을 사용하여 해당 빈을 조회할 수 있다.
    @Test
    @DisplayName("Find Bean By Name")
    void findBeanByName(){
        // getBean(이름): 해당 빈을 Object 타입으로 반환
        Object memberService = ac.getBean("memberService");
        Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }

  1. 빈 타입으로 조회하기
    ac.getBean(타입)을 사용하여 해당 타입의 빈을 조회할 수 있다.
    ⚠️ 빈의 상속으로 인해, 자식 타입의 빈도 함께 조회된다.
    @Test
    @DisplayName("Find Bean By Type")
    void findBeanByType(){
        // getBean(타입): 해당 타입 + 자식 타입의 빈을 Object 타입으로 반환
        Object memberService = ac.getBean(MemberService.class);
        Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }

  1. 빈 이름 + 타입으로 조회하기
    ac.getBean(이름, 타입)으로 해당 빈을 조회할 수 있다.
    @Test
    @DisplayName("Find Bean By Name & Type")
    void findBeanByNameType(){
        // getBean(이름, 타입): 해당 빈을 Object 타입으로 반환
        Object memberService = ac.getBean("memberService",MemberService.class);
        Assertions.assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }
}

■ 중복되는 타입이 있을 때

ac.getBean(타입)을 사용했을 때, 해당되는 빈이 2개 이상 존재하면 오류가 발생한다.

AnnotationConfigApplicationContext ac = 
	new AnnotationConfigApplicationContext(SameTypeBeanConfig.class);

    @Configuration
    static class SameTypeBeanConfig{
        @Bean
        public BoardPolicy boardPolicy1(){
            return new ReadOnly();
        }

        @Bean
        public BoardPolicy boardPolicy2(){
            return new ReadOnly();
        }
    }

    @Test
    @DisplayName("Find Bean By Type")
    void findBeanByType(){
        // 해당 타입의 빈이 여러개 존재한다면, 중복 오류 발생
        Assertions.assertThrows(NoUniqueBeanDefinitionException.class, 
        	() -> ac.getBean(BoardPolicy.class));
    }

해결 방법은, ac.getBean(이름, 타입)을 사용하는 것이다.

⚠️ 특정 타입의 빈을 모두 조회하고 싶다면, getBeansOfType(타입)을 사용한다.

 	// 특정 타입의 빈을 모두 출력하고 싶다면, getBeansOfType()을 사용하면 된다.
   	@Test
    @DisplayName("Find ALL Bean By Type")
    void findAllBeanByType(){
        Map<String, BoardPolicy> beansOfType = ac.getBeansOfType(BoardPolicy.class);
        org.assertj.core.api.Assertions.assertThat(beansOfType.size()).isEqualTo(2);
    }

빈의 상속관계

ac.getBean(타입)을 사용하여 빈을 조회할 때, 자식 타입의 빈도 함께 조회된다.
ac.getBean(Object.class)는 모든 스프링 빈을 반환한다.

여기에서 나는 두가지 의문점이 생겼다.
1. 인터페이스 - 구현객체도 상속 관계라 볼 수 있는가?
2. 빈의 타입은 무엇인가?

위 두 의문점에 대한 답은, 인프런 질문 게시판에서 찾을 수 있었다.

  1. 인터페이스 - 구현객체도 상속 관계라 볼 수 있는가?
    요약: 인터페이스도 부모가 될 수 있다.

  2. 빈의 타입은 무엇인가?
    요약: @Bean이 달린 메소드의 리턴 값이 상위 객체라고 하더라도, 생성된 객체의 타입 정보는 하위 객체를 나타낸다.
    즉, 빈의 타입은 return new 생성자()로 생성된 객체 자체의 타입을 따라간다.
public class ParentBeanTest {
    // 부모 타입의 빈을 조회하면, 자식 타입의 빈도 함께 조회된다.
    AnnotationConfigApplicationContext ac = 
    	new AnnotationConfigApplicationContext(InheritBeanTest.class);

    @Configuration
    static class InheritBeanTest{
        @Bean
        public BoardPolicy boardPolicy1(){
            return new ReadOnly();
        }

        @Bean
        public BoardPolicy boardPolicy2(){
            return new ReadWrite();
        }
    }

    @Test
    @DisplayName("부모 타입 조회 시, 자식 타입도 모두 조회된다.")
    void findBeanByParentTypeError(){
        Assertions.assertThrows(NoUniqueBeanDefinitionException.class, 
        	() -> ac.getBean(BoardPolicy.class));
    }

BoardPolicy.class로 빈을 조회하면, BoardPolicy 인터페이스(부모)를 구현한 모든 구현 객체(자식)를 조회한다.

➜ ReadOnly, ReadWrite 객체 모두 조회됨. NoUniqueBeanDefinitionException 발생

위의 문제를 해결하기 위해서는, 이름 + 타입으로 조회하면 된다.

    @Test
    @DisplayName("부모 타입으로 조회할 때, 자식이 둘 이상 있으면 자식의 이름을 사용하면 된다.")
    void findBeanByParentTypeAndChildName(){
        BoardPolicy boardPolicy = ac.getBean("boardPolicy1",BoardPolicy.class);
        assertThat(boardPolicy).isInstanceOf(ReadOnly.class);
    }



✏️ BeanFactory & ApplicationContext

  • BeanFactory는 스프링 컨테이너의 최상위 인터페이스이다.
  • getBean(), getBeansTypeOf() 등의 메소드는 BeanFactory 인터페이스에 선언되어 있다.
  • ApplicationContext는 부가기능을 제공한다.

■ ApplicationContext의 부가기능

ApplicationContext는 BeanFactory뿐만 아니라 다양한 인터페이스를 상속한다.

상속하는 인터페이스추가되는 기능
MessageSource메시지 소스를 활용한 국제화
EnvironmentCapable환경변수 처리
ApplicationEventPublisher애플리케이션 이벤트 처리 지원
ResourceLoader리소스 조회
BeanFactory빈 조회 및 관리



✏️ BeanDefinition

A BeanDefinition describes a bean instance, which has property values, constructor argument values, and further information supplied by concrete implementations. - spring.io

BeanDefinition은 설정 메타 정보를 가지고 있다.

설정 메타 정보(Configuration metadata)는,

  1. 빈의 제작 방법
  2. 빈의 lifecycle 정보
  3. 빈의 의존관계

를 포함하고 있다.


■ 다른 설정 형식

앞선 예시에서는 자바 코드인 AppConfig 클래스를 설정 정보로 사용했다.
스프링 컨테이너는 XML, Groovy 등도 설정 정보로 받을 수 있다.

https://spring.io/projects/spring-framework

이것이 가능한 이유는, BeanDefinition 인터페이스를 기반으로 스프링 빈을 생성하기 때문이다.

컨테이너는 설정 정보가 자바 클래스인지, XML인지, Groovy인지 알 필요가 없다. BeanDefinition 인터페이스만으로 스프링 빈을 생성하기 때문이다.

설정 정보에 맞게 BeanDefinition을 구현하는 것은 구현체(AnnotatedBeanDefinitionReader, XMLBeanDefinitionReader 등)가 할 일이다.




REFERENCE

스프링 핵심 원리 - 김영한 개발자님

profile
백엔드 개발자

0개의 댓글