(Spring) 스프링 컨테이너와 스프링 빈

유인근·2021년 5월 12일
1

글 소개

김영한님의 스프링 핵심 원리 - 기본편 강의 내용 중 '4강. 스프링 컨테이너와 스프링 빈'에 대한 내용을 정리해봤다.

직전 강의 '3강. 스프링 핵심원리 이해2 - 객체 지향 원리 적용'에서는 다형성만으로는 해결이 안되는 OCP, DIP에 대한 문제를 DI(의존관계 주입)를 통해 어떻게 해결했는지 다뤘다.
(*관련 글: https://yunb2.tistory.com/33)

그리고 'DI(의존관계 주입)를 할 때 필요한 곳에 직접 자바 객체를 생성하는 것이 아닌, 스프링 컨테이너에서 객체를 빈으로 등록하고 필요할 때마다 찾아서 쓸 때 장점은 어떤게 있을까?' 라는 질문으로 강의가 마무리가 됐다. 이번 강의부터는 스프링 컨테이너의 핵심 기능을 배우면서 이에 대한 답을 조금씩 찾아갈 것 같다.

스프링 컨테이너 생성

// 스프링 컨테이너인 ApplicationContext 생성
ApplicationContext applicationContext 
			= new AnnotationConfigApplicationContext(AppConfig.class);
  • ApplicationContext는 스프링 빈에 대한 정보를 저장하는 스프링 컨테이너다.
  • 스프링 컨테이너 만드는 방법
    • XML 기반
    • Annotation 기반

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

  • 구성 정보를 지정해 스프링 컨테이너 생성
// 이 강의에서 구성 정보는 클래스는 AppConfig에 해당
ApplicationContext applicationContext 
			= new AnnotationConfigApplicationContext(AppConfig.class);
  • 구성 정보에 있는 설정 클래스 정보를 통해 빈 등록
    • 메소드 이름이 빈 이름
    • name 속성으로 이름을 따로 지정할 수도 있다. ex) @Bean(name="memberService2")
    • 🌟 (주의) 빈 이름은 항상 다른 이름을 지정해야 한다.
      이름이 같을 시에, 다른 빈이 무시되거나 덮어씌어지는 오류가 발생한다.
  • 의존 관계 설정
    • 설정 정보를 참고해서 의존 관계를 주입(DI)한다.

컨테이너에 등록된 빈 조회

  • 스프링 컨테이너에서 스프링 빈을 찾는 가장 기본적인 조회 방법은 다음과 같다
    • ac.getBean(빈 이름)
    • ac.getBean(타입)
    • ac.getBean(빈 이름, 타입)
    • 단, 조회 대상이 되는 빈이 없으면 예외(NoSuchBeanDefinitionException)가 발생한다.
    • 그리고 구체 타입으로 조회하면 유연성이 떨어진다.
  • 모든 빈 조회
    • ac.getBeanDefinitionName()을 통해 스프링에 등록된 모든 빈 이름을 조회 가능
    • ac.getBean()을 통해 빈 이름으로 빈 객체를 조회 가능
    • 코드를 실행해 출력해보면 내가 생성한 빈 뿐만 아니라, 스프링에서 사용하기 위해 디폴트로 생성되는 빈까지 모두 출력되는 것을 알 수 있다.
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) ;
        }
    }
}

  • 애플리케이션 빈 조회
    • 스프링 내부에서 사용하는 빈은 getRole()로 구분 가능
      • ROLE_APPLICATION: 일반적으로 사용자가 정의한 빈
      • ROLE_INFRASTRUCTURE: 스프링이 내부에서 사용하는 빈
class ApplicationContextInfoTest {

    AnnotationConfigApplicationContext ac 
    			= new AnnotationConfigApplicationContext(AppConfig.class);
    
    @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) ;
            }
        }
    }
}

(예시 코드 양이 많아질 것 같아서 여기서부터는 예시 코드와 출력 결과는 생략...)

  • 동일한 타입이 둘 이상
    • 같은 타입의 스프링 빈이 둘 이상이면 오류가 발생하므로, 이때는 빈 이름을 지정해야 한다.
  • 상속 관계에서 빈 조회
    • 부모 타입으로 빈을 조회하면, 모든 자식 타입도 함께 조회되게 된다.
      => 왜 그렇게 만들었을까? 강의에서는 기본 대원칙이라고 하는데, 뒤의 강의에서 이 원칙이 활용되는 개념을 다룰 것 같은 느낌이다.
    • 그래서 Object 타입으로 빈을 조회하면, 모든 스프링 빈을 조회하게 된다.

BeanFactory와 ApplicationContext

  • BeanFactory

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

    • BeanFactory의 기능을 모두 상속받음
    • 애플리케이션을 개발할 때 빈 관리, 조회 뿐만 아니라 수 많은 부가기능이 필요한데 이를 위해 ApplicationContext 사용
    • 그래서 BeanFactory를 직접 사용할 일은 거의 없다. 부가기능이 포함된 ApplicationContext를 사용한다.
    • 부가기능: 메세지 소스를 활용한 국제화 기능, 환경변수, 애플리케이션 이벤트 등
      (*출처: 김영한 님의 스프링 핵심 - 기본편 강의 자료)

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

  • 자바 코드로 설정하기
ApplicationContext applicationContext 
			= new AnnotationConfigApplicationContext(AppConfig.class);
  • XML 형식으로 설정하기
    • 스프링 부트를 사용하면서 거의 사용하지 않음
    • XML을 사용하면 컴파일 없이 빈 설정 정보를 바꿀 수 있는 장점이 있음
public class XmlAppContext {

    @Test
    void xmlAppContext() {
        ApplicationContext ac = new GenericXmlApplicationContext("appConfig.xml");
        MemberService memberService = ac.getBean("memberService", MemberService.class);
        assertThat(memberService).isInstanceOf(MemberService.class);
    }
}
<?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>

스프링 빈 설정 메타 정보

  • BeanDefinition
    • 위와 같이 스프링 컨테이너를 생성하는 설정 형식을 다양하게 지원할 수 있는 이유는 바로 역할과 구현을 개념적으로 잘 나눴기 때문
      (*출처: 김영한 님의 스프링 핵심 - 기본편 강의 자료)
  • 실무에서 BeanDefinition을 직접 정의하거나 사용할 일은 거의 없다고 한다.
  • 하지만 BeanDefinition으로 추상화를 한 덕분에 다양하게 설정을 할 수 있다는 것은 기억하자!
profile
끊임없이 성장하는 개발자 🔥

0개의 댓글