본문 바로가기
[ Concept ]

[ Concept ] 스프링 컨테이너와 스프링 빈

by 환이s 2023. 5. 19.

 


스프링 컨테이너(Spring Container)

 

스프링 컨테이너는 스프링 프레임워크의 핵심 컴포넌트입니다. 스프링 컨테이너는 자바 객체의 생명 주기를 관리하며, 생성된 자바 객체들에게 추가적인 기능을 제공합니다.

 

스프링에서는 자바 객체를 빈(Bean)이라고 하는데, 스프링 컨테이너에서는 이 빈의 생성부터 소멸까지를 개발자 대신 관래해주는 곳이라고 할 수 있습니다.

 

더 정확히는 스프링 컨테이너를 부를 때 BeanFactory , ApplicationContext로 구분해서 이야기하는데, BeanFactory를 직접 사용하는 경우는 거의 없으므로 일반적으로 ApplicationContext를 스프링 컨테이너라고 합니다.

 

위 사진은 스프링 컨테이너가 제공하는 부가기능을 나열한 표입니다.

 

각 기능은 다음과 같은 특징이 있습니다.

 

  • BeanFactory

 

  • 스프링 컨테이너의 최상위 인터페이스다.
  • 스프링 빈을 관리하고 조회하는 역할을 담당한다.
  • getBean()을 제공한다.
  • 지금까지 우리가 사용했던 대부분의 기능은 BeanFactory가 제공하는 기능이다.

 

  • ApplicationContext BeanFactory

 

  • 기능을 모두 상속받아서 제공한다.
  • 빈을 관리하고 검색하는 기능을 BeanFactory가 제공해 주는데, 그러면 둘의 차이가 뭘까?
  • 애플리케이션을 개발할 때는 빈을 관리하고 조회하는 기능은 물론이고, 수많은 부가기능이 필요하다.

 

  • 메시지소스를 활용한 국제화 기능(MessageSouce)

 

  • 예를 들어서 한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로 출력

 

 

  • 환경변수(EnvironmentCapable)

 

  • 로컬, 개발, 운영 등을 구분해서 처리

 

 

  • 애플리케이션 이벤트(ApplicationEvenpublisher)

 

  • 이벤트를 발행하고 구독하는 모델을 편리하게 지원

 

 

  • 편리한 리소스 조회(ResourceLoader)

 

  • 파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회

 

또한 스프링 컨테이너에 객체, 빈을 등록하는 이유는 스프링이 각 객체 간 의존관계를 관리하도록 하는데에 큰 목적이 있습니다.

객체가 의존관계를 등록할 때는 스프링 컨테이너에서 해당하는 빈을 찾고, 그 빈과 의존성을 만듭니다.

 

좀 더 이해하기 쉽게 스프링 컨테이너 예제 코드를 통해서 알아봅니다.

 

//스프링 컨테이너 생성

ApplicationContext applicationContext =
 new AnnotationConfigApplicationContext(AppConfig.class);

 

위 코드는 스프링 컨테이너를 생성하는 코드입니다. 

 

앞서 말씀드린 것처럼 ApplicationContext 스프링 컨테이너이며, 구현체로는  AnnotationConfigApplicationContext가 있습니다.  AnnotationConfigApplicationContext의 매개변수에 구성 정보를 지정해주어야 하는데, 위 코드는 AppConfig.class를 구성 정보로 지정했습니다.

 

그럼 구성 정보로 지정한 AppConfig.class 파일 소스를 확인해 봅시다.

 

// 애플리케이션의 전체 동작 방식을 구성(config)하기 위해, "구현 객체를 생성"하고 "연결"하는
// 책임을 가지는 별도의 설정 클래스
//AppConfig는 애플리케이션의 실제 동작에 필요한 "구현 객체를 생성"한다.
//즉, 객체의 생성과 연결은 "AppConfig"가 담당한다.

//MemberServiceImpl
//MemoryMemberRepository
//OrderServiceImpl
//FixDiscountPolicy

//AppConfig는 생성한 객체 인스턴스의 참조(래퍼런스)를 "생성자를 통해서 주입(연결)"해준다.
//MemberServiceImpl -> MemoryMemberRepository
//OrderServiceImpl -> MemoryMemberRepository , FixDiscountPolicy4

//정리
//AppConfig를 통해서 관심사를 확실하게 분리했다.
//배역, 배우를 생각해보자
//AppConfig는 공연 기획자다.
//AppConfig는 구체 클래스를 선택한다. 배역에 맞는 담당 배우를 선택한다.
//애플리케이션이 어떻게 동작해야 할지 전체 구성을 책임진다.
//이제 각 배우들은 담당 기능을 실행하는 책임만 지면 된다.
//orderServiceImpl 은 기능을 실행하는 책임만 지면 된다.

@Configuration // 설정 정보에 Configuration을 적용 시켜준다.
public class AppConfig {
    // 중복 코드 , 리팩터링 new MemoryMemberRepository() ,  new FixDiscountPolicy()
    // 역할이 한눈에도 볼 수 있게 처리해줘야 한다.

    @Bean
    public MemberService memberService(){
        // MemberService를 요청하면 Impl 리턴 시 MemoryMemberRepository 생성해서 보냄
        return new MemberServiceImpl(memberRepository());

    }
    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
    @Bean
    public OrderService orderService(){
        return new OrderServiceImpl(memberRepository(),  discountPolicy());
    }
    @Bean
    public DiscountPolicy discountPolicy(){
      //  return new FixDiscountPolicy();
        return new RateDiscountPolicy();

    }
}

 

위 코드를 확인해 보면 구성 정보로 쓰인 파일은 Bean을 정의한 @Configuration을 적용한 클래스 파일입니다.

 

결론적으로 스프링 컨테이너는 앞서 말씀드린 것처럼 Bean을 생성부터 소멸까지 관리해 주는 곳입니다.

그렇다면 스프링 컨테이너를 사용하는 이유에 대해서 대략적으로 알 수 있습니다.

 

먼저, 객체를 생성하기 위해서는 new 생성자를 사용해야 합니다. 하지만 그로 인해 애프릴케이션에서는 수많은 객체가 존재하고 서로를 참조하게 됩니다. 객체 간의 참조가 많으면 많을수록 의존성이 높아지게 되는데, 이는 낮은 결합도와 높은 캡슐화를 지향하는 객체지향 프로그래밍의 핵심과는 먼 방식입니다.

 

따라서, 객체 간의 의존성을 낮추어 결합도는 낮추고, 높은 캡슐화를 위해 스프링 컨테이너를 사용합니다.

 

그렇다면 컨테이너에서 관리해 주는 스프링 빈은 대체 무엇일까??

 

스프링 빈(Bean)

 

앞서 말씀드린 것처럼 자바 객체를 빈(Bean)이라고 합니다.

빈(Bean)은 스프링 컨테이너에 의해 관리되는 재사용 가능한 소프트웨어 컴포넌트입니다.

 

빈은 인스턴스화된 객체를 의미하며, 스프링 컨테이너에 등록된 객체를 스프링 빈(Spring Bean)이라고 합니다.

@Bean 어노테이션을 통해 메서드로부터 반환된 객체를 스프링 컨테이너에 등록해서 사용하며, 빈은 클래스의 등록 정보, Getter/Setter 메서드를 포함해서 컨테이너에 사용되는 설정 메타테이터로 생성됩니다.

 

 

 

 Bean Method

 

ApplicationContext 타입의 변수를 ac라고 가정하고 ac.메서드명 형태로 아래 메서드들을 사용할 수 있습니다.

 

ac.getBean() - 특정 스프링 빈을 조회하는 데 사용됩니다.

 

매개변수로 아래 코드처럼 여러 종류가 올 수 있습니다.

 // 스프링 빈 조회 - 기본
    // 스프링 컨테이너에서 스프링 빈을 찾는 가장 기본적인 조회 방법
    // ac.getBean(빈이름, 타입)
    // ac.getBean(타입)

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    @Test
    @DisplayName("빈 이름으로 조회")
    void fintBeanByName(){
        MemberService memberService = ac.getBean("memberService" , MemberService.class);

        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }

 

ac.getBeanDefinitionNames() -> 스프링에 등록된 모든 빈 이름을 조회합니다.

 

  • ROLE_APPLICATION : 일반적으로 사용자가 정의한 빈
  • ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 

 

public class ApplicationContextInfoTest {
    //모든 빈 출력하기
    //실행하면 스프링에 등록된 모든 빈 정보를 출력할 수 있다.
    //ac.getBeanDefinitionNames() : 스프링에 등록된 모든 빈 이름을 조회한다.
    //ac.getBean() : 빈 이름으로 빈 객체(인스턴스)를 조회한다.
    //애플리케이션 빈 출력하기
    //스프링이 내부에서 사용하는 빈은 제외하고, 내가 등록한 빈만 출력해보자.
    //스프링이 내부에서 사용하는 빈은 getRole() 로 구분할 수 있다.
    //ROLE_APPLICATION : 일반적으로 사용자가 정의한 빈
    //ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈

    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);
        }

    }
    @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);
            }
        }

    }
}

 

ac.getBeansOfType() -> 해당 타입의 모든 빈을 조회할 수 있다.

 

public class ApplicationContextSameBeanFindTest {
    //스프링 빈 조회 - 동일한 타입이 둘 이상
    // 타입으로 조회 시 같은 타입의 스프링 빈이 둘 이상이면 오류가 발생한다. 이때는 빈 이름을 지정하자.
    // ac.getBeansOfType() 을 사용하면 해당 타입의 모든 빈을 조회할 수 있다.

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);


    @Test
    @DisplayName("타입으로 조회 시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다.")
    void findBeanByTypDuplicate(){
        //MemberRepository bean = ac.getBean(MemberRepository.class);
        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();
        }
    }
}

 


마치며

 

오늘은 스프링 컨테이너와 스프링 빈에 대해서 포스팅해보았습니다.

다음 포스팅에서 뵙겠습니다.

728x90