스프링 핵심 원리 Section 4. 스프링 컨테이너와 스프링 빈

Bae YuSeon·2024년 5월 15일
0

spring스터디

목록 보기
11/15
post-thumbnail

김영한 강사님이 제공하시는 인프런 - "스프링 입문 - 스프링 핵심 원리 - 기본편" 강의를 듣고 정리한 내용입니다
강의 링크
김영한 - 스프링 핵심 원리 - 기본편 (유료강의)


1강) 스프링 컨테이너 생성

  • 스프링 컨테이너
    • ApplicationContext = 스프링 컨테이너
      - 스프링 컨테이너 생성 방법
      1. XML 기반으로 생성
        2.애노테이션 기반 자바 설정 클래스로 생성
        • 직전에 AppConfig를 사용했던 방식
    • 스프링 컨테이너 생성
      ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
      • ApplicaionContext = 인터페이스
      • AnnotationConfigApplicaionContext 클래스 = ApplicationContext 인터페이스의 구현체
    • 스프링 컨테이너 생성 과정
      1. 스프링 컨테이너 생성
        스프링 컨테이너 생성
        • 스프링 컨테이너를 생성할 때 구성 정보 AppConfig.class를 파라미터 값으로 넘겨주어야 한다
      2. 스프링 빈 등록
        스프링 빈 등록
        • 빈 이름은 메서드 이름을 사용하고, 직접 부여할 수 도 있다
          @Bean(name="memberService2")
        • 빈 이름은 항상 다른 이름을 부여해야 한다
      3. 스프링 빈 의존관계 설정 - 준비
        스프링 빈 의존관계 설정 - 준비
      4. 스프링 빈 의존관계 설정 - 완료
        스프링 빈 의존관계 설정 - 완료
    • 스프링 컨테이너는 1.BeanFactory, 2.ApplicationContext 로 구분

⇒ 정리하면
1. 스프링 컨테이너 생성
2. 설정(구성) 정보 참고해서 스프링 빈 등록
3. 의존관계 설정


2강) 컨테이너에 등록된 모든 빈 조회

스프링 컨테이너에 등록한 스프링 빈 확인
src/test/java/hello.core/beanfind/ApplicationContextBasicFindTest.java

class ApplicationContextInfoTest {

    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
    
    @Test
    @DisplayName("모든 빈 출력하기")
    void findAllBean() {
        // 스프링 컨테이너에 등록된 모든 빈의 이름 조회
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            // 빈 이름(key)로 빈(value) 얻기
            Object bean = ac.getBean(beanDefinitionName);
            System.out.println("bean = " + beanDefinitionName + " object = " + bean);
        }
    }
    @Test
    @DisplayName("애플리케이션 빈 출력하기")
    void findApplicationBean() {
        // 스프링 컨테이너에 등록된 모든 빈의 이름 조회
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            // 빈에 대한 메타데이터 정보
            BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);

            // ROLE_APPLICATION : 일반적으로 사용자가 정의한 빈
            // ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈
            if (beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
                // 빈 이름(key)로 빈(value) 얻기
                Object bean = ac.getBean(beanDefinitionName);
                System.out.println("bean = " + beanDefinitionName + " object = " + bean);
            }
        }
    }
}
  • 모든 빈 출력하기

    • ac.getBeanDefinitionNames() : 스프링에 등록된 모든 빈 이름 조회
    • ac.getBean() : 빈 이름으로 빈 객체(인스턴스) 조회
      모든 빈 출력 실행 결과
  • 애플리케이션 빈 출력하기

    • 스프링이 내부에서 사용하는 빈 getRole() 로 구분
    • ROLE_APPLICATION : 일반적으로 사용자가 정의한 빈
    • ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈
      애플리케이션 빈 출력 실행 결과

3강) 스프링 빈 조회 - 기본

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

src/test/java/hello.core/beanfind/ApplicationContextBasicFindTest.java

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);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }

    // 실패 테스트
    @Test
    @DisplayName("빈 이름으로 조회X")
    void findBeanByNameX() {
        // 오른쪽 로직을 실행했을 때 왼쪽 예외가 터져야 테스트 성공
        ////ac.getBean("xxxxx", MemberService.class);
        assertThrows(NoSuchBeanDefinitionException.class,
                () -> ac.getBean("xxxxx", MemberService.class));
    }
}
  • 빈 이름 & 타입으로 빈 조회
    • 빈 이름 = memberService, 타입 = MemberService 인 빈을 조회
    • 조회한 빈의 인스턴스 타입이 MemberServiceImpl이 맞는지 검증
  • 타입만으로 빈 조회
    • 타입 = MemberService 인 빈을 조회
    • 조회한 빈의 인스턴스 타입이 MemberServiceImpl이 맞는지 검증
  • 빈 이름 & 구체 타입으로 빈 조회
    • 빈 이름 = memberService, 타입 = MemberServiceImpl 인 빈을 조회
    • 조회한 빈의 인스턴스 타입이 MemberServiceImpl이 맞는지 검증
  • 빈 이름 & 타입으로 빈 조회되지 않는 경우
    • 빈 이름 = xxxxx, 타입 = MemberService 인 빈을 조회
    • NoSuchBeanDefinitionException 예외를 발생시키는지 검증

스프링 빈 기본 조회 실행 결과


4강) 스프링 빈 조회 - 동일한 타입이 둘 이상

ac.get(타입)으로 조회할 때, 같은 타입의 스프링 빈이 둘 이상이면 NoUniqueBeanDefinitionException 예외가 발생!
ac.get(빈 이름, 타입) 으로 1개의 빈을 조회
ac.getBeansOfType() 으로 해당 타입의 모든 빈 조회 가능

src/test/java/hello.core/beanfind/ApplicationContextSameBeanFindTest.java

public class ApplicationContextSameBeanFindTest {
    // 스프링 컨테이너 생성
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);

    // 실패 테스트
    @Test
    @DisplayName("타입으로 조회시 같은 타입이 둘 이상 있으면, 중복 오류가 발생한다")
    void findBeanByTypeDuplicate() {
        //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);
        // MemberRepository 타입의 빈이 2개인지 확인
        assertThat(beansOfType.size()).isEqualTo(2);
    }

    // static: ApplicationContextSameBeanFindTest 클래스에서만 쓰겠다
    @Configuration
    static class SameBeanConfig {
        // 빈의 이름이 다르고 객체 인스턴스 타입이 같음(파라미터 값이 다를 수 있음)
        @Bean
        public MemberRepository memberRepository1() {
            return new MemoryMemberRepository();
        }

        @Bean
        public MemberRepository memberRepository2() {
            return new MemoryMemberRepository();
        }
    }
}
  • findBeanByTypeDuplicate()
    • getBean(타입)으로 조회시 같은 타입이 2개 이상이면 NoUniqueBeanDefinitionException 예외가 발생
  • findBeanByName()
    • 같은 타입이 2개 이상이어도 getBean(빈 이름, 타입)으로 조회하면 성공
  • findAllBeanByType()
    • 특정 타입을 가진 모든 빈 조회
  • SameBeanConfig
    • 설정 정보 클래스
    • 이 테스트 클래스 내에서만 사용하기 위해 만든 Config
    • 이 테스트 내에서만 사용하므로 static으로 선언

동일한 타입이 둘 이상일 경우 빈 조회 실행 결과


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

부모 타입으로 조회하면, 자식 타입도 함께 조회되어 모든 자바 객체의 최고 부모인 Object 타입으로 조회하면, 모든 스프링 빈을 조회한다.
스프링 빈 조회 상속 관계
src/test/java/hello.core/beanfind/ApplicationContextExtendsFindTest.java

public class ApplicationContextExtendsFindTest {
    // 스프링 컨테이너 생성
    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);

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

        // DiscountPolicy 타입인 빈의 정보 출력(빈 이름, 빈 객체)
        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 {
        // 부모 타입이 DiscountPolicy인 2개의 빈
        @Bean
        public DiscountPolicy rateDiscountPolicy() {
            return new RateDiscountPolicy();
        }

        @Bean
        public DiscountPolicy fixDiscountPolicy() {
            return new FixDiscountPolicy();
        }
    }
}
  • findBeanByParentTypeDuplicate()
    • getBean(부모 타입)으로 조회할 때, 자식이 둘 이상이면 NoUniqueBeanDefinitionException 예외 발생
  • findBeanByParentTypeBeanName()
    • 자식이 2개 이상이어도 getBean(이름, 부모 타입)으로 조회하면 성공
  • findBeanBySubType()
    • getBean(타입)으로 특정 하위 타입으로 조회
    • 특정 하위 타입으로 조회하는 것은 좋지 않은 방식
  • findAllBeanByParentType()
    • getBeansOfType(부모 타입)으로 부모 타입+하위 타입의 빈 조회
  • findAllBeanByObjectType()
    • getBeansOfType(Object)로 모든 빈 조회
  • estConfig
    • 설정 정보 클래스
    • 이 테스트 클래스내에서만 사용하기 위해 만든 Config 클래스
    • 이 테스트 내에서만 사용하므로 static으로 선언

부모 타입 조회 실행 결과

정리하면

  • getBeanDefinitionNames(): ApplicationContext(스프링 컨테이너)에 등록된 모든 스프링 빈 이름 조회
  • getBean(타입): ApplicationContext(스프링 컨테이너)에 등록된 해당 타입의 스프링 빈 객체 조회
  • getBean(빈 이름, 타입): ApplicationContext(스프링 컨테이너)에 등록된 (빈 이름, 타입)의 스프링 빈 객체 조회
  • getBeansOfType(타입): ApplicationContext(스프링 컨테이너)에 등록된 해당 타입의 모든 스프링 빈 객체 조회

6강) BeanFactory와 ApplicationContext

  • BeanFactory

    • 스프링 컨테이너의 최상위 인터페이스
    • 스프링 빈을 관리, 조회하는 역할을 담당
  • ApplicationContext

    • BeanFactory 기능을 모두 상속받아 제공
    • 빈을 관리, 조회하는 기능에 추가로 부가 기능을 제공
    • ApplicatonContext가 제공하는 부가기능
      ApplicatonContext가 제공하는 부가기능
      • 메시지소스를 활용한 국제화 기능(MessageSource)
        예를 들어 한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로 출력
      • 환경변수(EnvironmentCapable)
        로컬 환경(내 PC), 개발(테스트) 환경, 운영 환경 등을 구분해서 처리
      • 애플리케이션 이벤트(ApplicationEventPublisher)
        이벤트를 발행하고 구독하는 모델을 편리하게 지원
      • 편리한 리소스 조회(ResourceLoader)
        파일, 클래스패스, 외부 등에서 리소스를 편리하게 조회

7강) 다양한 설정 형식 지원 - 자바 코드, XML

  • 스프링 컨테이너는 다양한 형식의 설정 정보를 받아들일 수 있게 유연하게 설계

    • 자바 코드, XML, Groovy 등등
  • 애노테이션 기반 자바 코드 설정 사용

    • new AnnotationConfigApplicationContext(AppConfig.class)
          ```  
    • AnnotationConfigApplicationContext 클래스를 사용하면서 자바 코드로된 설정 정보를 넘기면 된다.
  • XML 설정 사용

    • 최근에는 스프링 부트를 많이 사용하면서 XML기반의 설정은 잘 사용하지 않는다.
    • GenericXmlApplicationContext를 사용하면서 xml 설정 파일을 넘기면 된다
    • XmlAppConfig 사용 자바 코드
       public class XmlAppContext {
       
        @Test
        void xmlAppContext() {
           // xml 설정파일로 스프링 컨테이너 생성
           ApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
           MemberService memberService = ac.getBean("memberService", MemberService.class);
           assertThat(memberService).isInstanceOf(MemberService.class);
        }
      }
    • xml 기반의 스프링 빈 설정 정보
      (src/main/resources/appConfig.xml)
      - xml 기반의 appConfig.xml 스프링 설정 정보와 자바코드로 된 AppConfig.java 설정 정보는 거의 비슷
      - 스프링 공식 문서에서 xml 기반 설정에 대해 자세한 정보 확인 가능

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

스프링이 다양한 설정 형식을 지원할 수 있는 이유는?
BeanDefinition이라는 추상화
⇒ 역할과 구현을 개념적으로 나눈 것

  • BeanDefinition

    • 빈 설정 메타정보
    • @Bean, <bean> 당 각각 하나씩 메타 정보가 생성
    • 스프링 컨테이너는 이 메타정보를 기반으로 스프링 빈을 생성
      BeanDefinition
  • BeanDefinition 정보

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

    public class BeanDefinitionTest {
    
      // getBeanDefintion을 하기 위해 ApplicationContext로 선언하지 않음
      AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
      //GenericXmlApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
    
       @Test
       @DisplayName("빈 설정 메타정보 확인")
       void findApplicationBean() {
           // 모든 빈 이름 조회
           String[] beanDefinitionNames = ac.getBeanDefinitionNames();
           for (String beanDefinitionName : beanDefinitionNames) {
               // 빈 이름으로 beanDefintion 조회
               BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);
               // 사용자가 등록한 빈의 메타정보만 출력
               if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
                   System.out.println("beanDefinitionName = " + beanDefinitionName +
                         " beanDefinition = " + beanDefinition);
               }
            }
         }
      }

    BeanDefinition 테스트 실행 결과

0개의 댓글

관련 채용 정보