[스프링 핵심 원리 - 기본편] 4. 스프링 컨테이너와 스프링 빈

HJ·2022년 7월 30일
0

김영한 님의 스프링 핵심 원리 - 기본편 강의를 보고 작성한 내용입니다.
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard


1. 스프링 컨테이너 생성

// 스프링 컨테이너 생성
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
  • ApplicationContext : 스프링 컨테이너 & 인터페이스

  • ApplicationContext의 구현체 : AnnotationConfigApplicationContext

  • 스프링 컨테이너는 XML을 기반으로 만들 수 있고, 어노테이션 기반의 자바 설정 클래스로 만들 수 있다

  • 위의 코드는 어노테이션 기반의 자바 설정 클래스로 스프링 컨테이너를 만든 것이다




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

2-1. 스프링 컨테이너 생성

  • new AnnotationConfigApplicationContext(AppConfig.class)

  • 스프링 컨테이너를 생성할 때는 구성 정보를 지정해주어야 함

  • 위에서는 AppConfig.class가 구성정보

  • 스프링 컨테이너가 AppConfig.class의 구성 정보를 활용해서 객체를 생성한다

  • 스프링 컨테이너 내부에는 스프링 빈 저장소가 있다


2-2. 스프링 빈 등록

  • @Bean이 붙어있는 메소드의 이름을 빈 이름(key 값)으로, 메소드가 반환하는 객체를 빈 객체로 스프링 빈 저장소에 등록

  • @Bean(name="memberService2") 처럼 빈 이름을 직접 부여할 수도 있다

  • 빈 이름은 항상 다른 이름을 부여해야 한다


2-3. 스프링 빈 의존 관계 설정

  • 스프링 컨테이너는 구성 정보 클래스 파일에서 객체의 생성자에 나타나는 의존 관계를 참고하여 의존관계를 주입한다

  • 스프링은 빈을 생성하는 단계와 의존관계를 주입하는 단계가 나누어져 있지만, 자바 코드로 스프링 빈을 등록하면 생성자를 호출하면서 의존관계 주입도 한 번에 처리된다




3. 스프링 빈 조회

3-1. 컨테이너에 등록된 모든 스프링 빈 조회


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);
        }
    }
}
  • ac.getBeanDefinitionNames() : 스프링에 등록된 모든 빈 이름을 조회

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


3-2. 애플리케이션 빈 조회


    @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);
            }
        }
    }
  • BeanDefinition.ROLE_APPLICATION : 애플리케이션 개발을 위해 직접 등록한 애플리케이션 빈 (일반적으로 사용자가 정의한 빈)

  • BeanDefinition.ROLE_INFRASTRUCTURE : 스프링이 내부에서 사용하는 빈




4. 스프링 빈 조회 - 기본

  • ac.getBean(빈이름, 타입)

  • ac.getBean(타입)

  • 조회 대상 스프링 빈이 없으면 예외 발생

    • NoSuchBeanDefinitionException: No bean named 'xxxxx' available

4-1. 빈 이름 + 인터페이스로 조회

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

        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }
  • memberService : 빈 이름 (메소드 이름)

  • 등록된 객체는 MemberServiceImpl의 인스턴스

  • 위 방식은 MemberService라는 인터페이스로 조회하는 방식

  • 인터페이스 이름으로 조회하면 인터페이스의 구현체가 대상이 된다


4-2. 빈 이름 + 구체 타입으로 조회


    @Test
    @DisplayName("구체 타입으로 조회")
    void findBeanByName2() {
        MemberService memberService = ac.getBean("memberService", MemberServiceImpl.class);
        assertThat(memberService).isInstanceOf(MemberServiceImpl.class);
    }
  • 스프링 컨테이너에 MemberServiceImpl라는 객체가 등록되어 있으면 조회가 된다

4-3. 스프링 빈 조회 실패 테스트


    @Test
    @DisplayName("빈 이름으로 조회 실패")
    void findBeanByNameX() {
        //ac.getBean("xxxx", MemberService.class);
        assertThrows(NoSuchBeanDefinitionException.class,
                () -> ac.getBean("xxxx", MemberService.class));
    }
  • 오른쪽 람다로 작성된 로직을 실행하면 왼쪽의 예외가 발생한다는 의미

  • ac.getBean("xxxx", MemberService.class)를 실행하면 NoSuchBeanDefinitionException이 발생한다

  • org.junit.jupiter.api.Assertions.*assertThrows()를 사용

  • assertThat()org.assertj.core.api.Assertions.*를 import




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

    @Configuration
    static class SameBeanConfig {
        @Bean
        public MemberRepository memberRepository1() {
            return new MemoryMemberRepository();
        }

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


    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SameBeanConfig.class);
  • SameBeanConfig를 스프링 컨테이너로 사용

  • 스프링 컨테이너에는 객체 타입이 MemberRepository 로 동일한 두 개의 빈이 존재


5-1. 타입으로 조회

    @Test
    @DisplayName("타입으로 조회 시 같은 타입이 둘 이상 있으면, 중복 오류 발생")
    void findBeanByTypeDuplicate() {
        assertThrows(NoUniqueBeanDefinitionException.class,
                () -> ac.getBean(MemberRepository.class));
    }
  • 타입으로 조회할 때 같은 타입의 스프링 빈이 둘 이상이면 오류가 발생

    • NoUniqueBeanDefinitionException
  • 이런 경우, 빈 이름을 지정해야함

    • 타입으로만 조회하는 것이 아니라 이름까지 사용해서 조회하는 방식을 사용

5-2. 빈 이름으로 조회

    @Test
    @DisplayName("타입으로 조회 시 같은 타입이 둘 이상 있으면, 빈 이름을 지정")
    void findBeanByName() {
        MemberRepository memberRepository = ac.getBean("memberRepository1", MemberRepository.class);
        assertThat(memberRepository).isInstanceOf(MemberRepository.class);
    }

5-3. 특정 타입 모두 조회

    @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);
    }
  • ac.getBeansOfType() 을 사용하면 해당 타입의 모든 빈을 조회할 수 있다

    • 반환형은 Map



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

  • 부모 타입으로 조회하면, 자식 타입도 함께 조회한다

  • 모든 자바 객체의 최고 부모인 Object 타입으로 조회하면, 모든 스프링 빈을 조회한다


6-1. 자식이 두 개 이상 있는 경우

    @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);
    }
  • 부모타입으로 조회했을 때 자식이 두 개 이상 있는 경우, 중복 오류 발생
  • 위에서 한 것처럼 이름까지 지정하여 조회해야함

6-2. 특정 하위 타입으로 조회

    @Test
    @DisplayName("특정 하위 타입으로 조회")
    void findBeanBySubType() {
        RateDiscountPolicy bean = ac.getBean(RateDiscountPolicy.class);
        assertThat(bean).isInstanceOf(RateDiscountPolicy.class);
    }
  • 자식이 두 개 이상 있는 경우, 특정 하위 타입으로 조회할 수 있음

  • but> 좋은 방법은 아님

  • 6-1 은 부모 타입에 해당하는 DisCountPolicy로 조회하면서 이름을 지정했는데 이번에는 특정 하위 타입 (자식 타입)으로 조회

  • ac.getBean()에 하위타입을 작성


6-3. 부모 타입으로 조회

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

        }
    }
  • ac.BeansOfType()Object.Class를 작성하면 스프링 내부적으로 사용하는 빈까지 모든 것이 출력됨



7. BeanFactory & ApplicationContext

7-1. BeanFactory

  • 스프링 컨테이너의 최상위 인터페이스

  • 스프링 빈을 관리하고 조회(검색)하는 역할을 담당

  • getBean() 을 제공

  • BeanFactory를 직접 사용할 일은 거의 없고 부가 기능이 포함된 ApplicationContext를 사용


7-2. ApplicationContext

  • BeanFactoryApplicationContext를 스프링 컨테이너라고 함

  • BeanFactory 기능을 모두 상속받아서 제공한다

  • ApplicatinContext = BeanFactory (빈 관리, 조회 기능) + 부가기능

  • ApplicationContext를 구현한 클래스 = AnnotationConfigApplicationContext

  • 애플리케이션을 개발할 때는 빈을 관리하고 조회하는 기능은 물론이고, 수 많은 부가기능이 필요


7-3. ApplicationContext가 제공하는 부가기능

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
		MessageSource, ApplicationEventPublisher, ResourcePatternResolver
  • ApplicationContext는 위의 인터페이스들을 extends한 인터페이스
  • 메시지소스를 활용한 국제화 기능

    • 파일을 분리해서 작성해놓고

    • 메세지 소스가 한국에서 들어오면 한국어로, 영어권에서 들어오면 영어로 된 파일을 출력

  • 환경변수

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

    • 로컬 : 내 PC에서 개발하는 환경

    • 개발 : 테스트 서버에 올려서 여러 시스템을 엮어 실제 테스트 서버에 띄워두고 테스트할 수 있는 서버

    • 운영 : 실제 프로덕션이 나감

    • 각 환경 별로 어떤 DB를 연결해야할지와 같은 것들이 다르기 때문에 환경변수와 관련된 정보를 처리해주는 기능

  • 애플리케이션 이벤트

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

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



8. 다양한 설정 형식 지원

  • 스프링 컨테이너는 다양한 형식의 설정 정보를 받아드릴 수 있다

    • 어노테이션 기반 자바 코드

    • xml : xml 문서를 설정 정보로 사용

    • 임의로 구현해서 설정 정보로 사용


8-1. 어노테이션 기반 자바 코드 설정 사용

  • AnnotationConfigApplicationContext 클래스를 사용하면서 자바 코드로된 설정 정보를 넘기면 된다

  • 여기서 자바 코드로 된 설정 정보 = AppConfig.class

  • new AnnotationConfigApplicationContext(AppConfig.class)

  • AppConfig는 @Configuration 어노테이션을 사용


8-2. XML 설정 사용

  • xml 문서를 설정 정보로 사용

  • xml을 사용하면 컴파일 없이 빈 설정 정보를 변경할 수 있다

  • GenericXmlApplicationContext 를 사용하면서 xml 설정 파일을 넘기면 된다

  • GenericXmlApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");

  • 아래 코드는 각각 Java, xml로 작성된 설정 정보 파일이지만 동일한 내용을 담고 있음


< appConfig.xml >

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

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

< AppConfog.class >

@Bean
public MemberService memberService() {
    return new MemberServiceImpl(memberRepository());
}

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



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

  • 스프링이 다양한 설정 형식을 지원할 수 있는 이유 : BeanDefinition이라는 추상화 ( 빈 정보에 대한 것 자체를 추상화 )

  • 역할과 구현을 나눈 것이라고 생각하면 된다

    • 자바 코드 혹은 xml 파일을 읽어서 BeanDefinitinon을 만든다
  • BeanDefinitinon : 빈 설정 메타정보

    • @Bean, <bean> 마다 각각 하나씩 메타 정보가 생성
  • 스프링 컨테이너는 어떤 파일이든 관계 없이 BeanDefinitinon 에만 의존하고 이 메타 정보를 기반으로 스프링 빈을 생성


  • AnnotationConfigApplicationContextAnnotatedBeanDefinitionReader 를 사용해서 AppConfig.class 를 읽고 BeanDefinition 을 생성한다

  • GenericXmlApplicationContextXmlBeanDefinitionReader 를 사용해서 appConfig.xml 설정 정보를 읽고 BeanDefinition 을 생성한다

  • 새로운 형식의 설정 정보가 추가되면, XxxBeanDefinitionReader를 만들어서 BeanDefinition 을 생성하면 된다




10. BeanDefinition 정보

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

    @Test
    @DisplayName("빈 설정 메타정보 확인")
    void findApplicationBean() {
        String[] beanDefinitionNames = ac.getBeanDefinitionNames();
        for (String beanDefinitionName : beanDefinitionNames) {
            BeanDefinition beanDefinition = ac.getBeanDefinition(beanDefinitionName);

            if(beanDefinition.getRole() == BeanDefinition.ROLE_APPLICATION) {
                System.out.println("beanDefinitionName = " + beanDefinitionName + "beanDefinitnion" + beanDefinition);
            }

        }
    }
  • BeanClassName: 생성할 빈의 클래스 명(자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)

  • factoryBeanName: 팩토리 역할의 빈을 사용할 경우 이름, 예) appConfig

  • factoryMethodName: 빈을 생성할 팩토리 메서드 지정, 예) memberService

  • Scope: 싱글톤(기본값)

  • lazyInit: 스프링 컨테이너를 생성할 때 빈을 생성하는 것이 아니라, 실제 빈을 사용할 때 까지 최대한 생성을 지연처리 하는지 여부

  • InitMethodName: 빈을 생성하고, 의존관계를 적용한 뒤에 호출되는 초기화 메서드 명

  • DestroyMethodName: 빈의 생명주기가 끝나서 제거하기 직전에 호출되는 메서드 명

  • Constructor arguments, Properties: 의존관계 주입에서 사용한다(자바 설정 처럼 팩토리 역할의 빈을 사용하면 없음)


  • BeanDefinition을 직접 생성해서 스프링 컨테이너에 등록할 수 도 있다. 하지만 실무에서 BeanDefinition을 직접 정의하거나 사용할 일은 거의 없다.

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

  • 가끔 스프링 코드나 스프링 관련 오픈 소스의 코드를 볼 때, BeanDefinition 이라는 것이 보일 때가 있다. 이때 이러한 메커니즘을 떠올리면 된다




참고> 인텔리제이 단축키

  • 이전에 작업했던 파일 : Ctrl + E

  • 리스트가 있을 때, 자동으로 for 문 완성 : iter + Tab

0개의 댓글