김영한 스프링 핵심 원리(기본편) - 스프링 컨테이너와 스프링 빈

개발할래·어제
0

개발

목록 보기
14/14
post-thumbnail

스프링 컨테이너, 스프링 빈 대해 알아보려고 한다.

스프링 컨테이너

  • ApplicationContext를 스프링 컨테이너라 함
  • AnnotationConfigApplicationContext : ApplicationContext(인터페이스) 구현 중 하나
  • xml을 기반 또는 애노테이션 기반의 자반 설정 클래스
  • 전 세션 AppConfig 방식 설정 -> 애노테이션 기반의 자바 설정 클래스로 스프링 컨테이너를 만듦

1. 스프링 컨테이너의 생성 과정

1) 스프링 컨테이너 생성

  • new AnnotationConfigApplicationContext(AppConfig.class);

  • AppConfig.class 구성 파일

    2) 스프링 빈 등록

    • AppConfig.class를 확인하고 스프링 빈 저장소에 빈을 등록
    • @Bean 어노테이션이 붙은 메서드의 이름으로 반환되는 빈 객체를 등록(빈의 이름은 직접 부여 가능)

    3) 스프링 빈 의존관계 설정

    • 준비 : 스프링 빈을 등록한 다음에 의존관계 설정 준비

    • 완료 : 스프링 컨테이너는 설정 정보를 참고해서 의존관계 주입

2. 컨테이너에 등록된 빈 조회

  • ApplicationContextInfoTest 파일 생성
public class ApplicationContextInfoTest {

    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);
             if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION){
                 Object bean = ac.getBean(beanDefinitionName);
                 System.out.println("name = " +beanDefinitionName + " object = " +bean);
             }
         }
    }
}
  • 정상적으로 테스트 완료 시 등록된 빈들 확인 가능

3. 기본적인 스프링 빈 조회

  • ApplicationContextBasicFindTest 생성
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);
    }

    @Test
    @DisplayName("빈 이름 조회 X")
    void findBeanByNameX(){
         assertThrows(NoSuchBeanDefinitionException.class,
                 () -> ac.getBean("xxx", MemberService.class));
    }


}

4. 동일한 타입이 둘이상 스프링 빈 조회

  • ApplicationContextSameBeanFindTest 생성
public class ApplicationContextSameBeanFindTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);

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



    static class SameBeanConfig{

        @Bean
        public MemberRepository memberRepository1(){ return new MemoryMemberRepository();}

        @Bean
        public MemberRepository memberRepository2(){ return new MemoryMemberRepository();}

    }

}
  • 타입으로 조회할 경우 같은 타입의 스프링 빈이 둘 이상이라면, 오류 발생 -> 빈 이름을 지정

5. 스프링 빈 조회 - 상속 관계

  • ApplicationContextExtendsFindTest 생성
public class ApplicationContextExtendsFindTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

    @Test
    @DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 중복 오류가 발생한다.")
    void findBeanByParentTypeDuplicate() {
        assertThrows(NoUniqueBeanDefinitionException.class, () -> ac.getBean(DiscountPolicy.class));
    }

    @Test
    @DisplayName("부모 타입으로 조회시, 자식이 둘 이상 있으면, 빈 이름을 지정하면 된다.")
    void findBeanByParentTypeBeanName() {
        DiscountPolicy rateDiscountPolicy = ac.getBean("rateDiscountPolicy", DiscountPolicy.class);
        assertThat(rateDiscountPolicy).isInstanceOf(RateDiscountPolicy.class);
    }

    @Test
    @DisplayName("특정 하위 타입으로 조회.")
    void findBeanBySubType() {
        RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
        assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
    }

    @Test
    @DisplayName("부모 타입으로 모두 조회하기.")
    void findAllBeanByParentType() {
        Map<String, DiscountPolicy> beansOfType = ac.getBeansOfType(DiscountPolicy.class);
        assertThat(beansOfType.size()).isEqualTo(2);
        for (String key : beansOfType.keySet()) {
            System.out.println("key = " + key + " value = " + beansOfType.get(key));
        }
    }

    @Test
    @DisplayName("부모 타입으로 모두 조회하기. - Object")
    void findAllBeanByObjectType() {
        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 TestConfig {
        @Bean
        public DiscountPolicy rateDiscountPolicy() { return new RateDiscountPolicy(); }

        @Bean
        public DiscountPolicy fixDiscountPolicy() { return new FixDiscountPolicy(); }
    }

}

6. BeanFactory와 ApplicationContext

  • BeanFactory

    • 스프링 컨테이너 최상위 인터페이스
    • 스프링 빈을 관리 및 조회하는 역할
    • getBean() 제공
  • ApplicationContext

    • BeanFatory 기능을 상속 받음
    • 빈을 관리하고 조회하는 기능 및 부가기능 제공
  • ApplicationContext 부가 기능

    • MessageSource : 주로 사용자에게 보여줄 메시지를 다국어로 제공할 때 사용
    • EnvironmentCapzble : 애플리케이션의 환경 설정(프로퍼티, 프로파일 등)에 대한 정보를 제공, 이를 통해 애플리케이션의 동작을 환경에 맞게 조정
    • ApplicationEventPublisher : 애플리케이션 내에서 이벤트를 발행하고, 이를 구독하는 리스너에게 전달하는 역할
      이를 통해 애플리케이션의 다양한 컴포넌트 간에 느슨한 결합을 유지하면서 통신 가능
    • ResourceLoader : 애플리케이션에서 외부 리소스를 쉽게 접근하고 사용 할 수 있도록 도와주는 역할.
      예) 파일 시스템, 클래스패스, URL 등에서 리소스를 로드 할 수 있음

7. 자바코드, XML, 기타 형식 지원

  • 상위 BeanFactory - ApplicationContext - AnnotationConfigApplicationContext/GenericXmlApplicationContext/ xxxApplicationContext

  • XML 설정

    • 대부분의 레거시 프로젝트 환경에서 XML로 되어있는 경우가 많음. XML을 사용하면 컴파일 없이 설정 정보 변경이
      가능
    • GenericXmlApplicationContext를 사용하면서 xml 설정 파일을 넘김
  • XML 설정을 코드로 작성
    • appConfig.xml 생성
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

     <bean id="memberService" class="hello.core.member.MemberServiceImpl">
          <constructor-arg name="memberRepository" ref="memberRepository"/>
     </bean>

    <bean id="memberRepository" class="hello.core.member.MemoryMemberRepository"/>

    <bean id="orderService" class="hello.core.order.OrderServiceImpl">
        <constructor-arg name="memberRepository" ref="memberRepository"/>
        <constructor-arg name="discountPolicy" ref="discountPolicy"/>
    </bean>
    <bean id="discountPolicy" class="hello.core.discount.RateDiscountPolicy"/>

</beans>
  • XmlAppContext.class 생성
public class XmlAppConfig {

    @Test
    void xmlAppContext(){
        ApplicationContext ac = new GenericXmlApplicationContext("classpath:appConfig.xml");
        MemberService memberService = ac.getBean("memberService", MemberService.class);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }
  • new GenericXmlApplicationContext(appConfig.xml) : classpath에 있는 appConfig.xml을 읽어 옮

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

8. 스프링 빈 설정 메타 정보 - BeanDefinition

  • 다양한 형식으로 스프링 지원
    : BeanDefinition

  • BeanDefinition을 빈 설정 메타정보라 함

    • @Bean, 각각 하나씩 메타 정보가 생성
  • 이 메타 정보를 기반으로 스프링 빈을 생성

    - 스프링 컨테이너는 BeanDefinition(인터페이스)에만 의존

상세하게 보자면

  • AnnotaionConfigApplicationContext는 AnnotatedBeanDefinitionReader를 사용해서 AppConfig.class를 읽고
    BeanDefinition을 생성 -> AnnotatedBeanDefinitionReader는 설정 정보를 읽어 빈 메타 정보를 생성
  • GenericXmlApplicationContext는 XmlBeanDefinitionReader를 사용해서 appConfig.xml 설정 정보를 읽고 BeanDefinition을
    생성
  • 새로운 형식의 설정 정보가 추가 시 xxxBeanDefinitionReader를 만들어 BeanDefintion을 생성

BeanDefinition 정보

  • BeanClassName: 생성할 빈의 클래스 명 (자바 설정처럼 팩토리 역할의 빈을 사용하면 없음 )
  • factoryBeanName: 팩토리 역할의 빈을 사용할 경우 이름, 예) appConfig
  • factoryMethodName: 빈을 생성할 팩토리 메서드 지정, 예) memberService
  • Scope: 싱글통(기본값)
  • lazyInit : 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 상용할 때까지 최대한 생성을 지연 처리 하는지 여부
  • InitMethodName: 빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명
  • DestoryMethodName: 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명
  • Constructor arguments, Properties : 의존관계 주입에서 사용 (자바 설정처럼 팩토리 역할의 빈을 사용하면 없음)

BeanDefinitionTest 생성

BeanDefinition에 대해 너무 깊게 이해하기 보다는, 스프링이 다양한 형태의 설정 정보를 BeanDefinition으로 추상화해서 사용 하는 정도만 이해

profile
내 인생부터 개발

0개의 댓글