[스프링/Spring] 스프링 컨테이너(Spring Container)와 스프링 빈(Spring Bean)에 대해 알아보자.

kindof

·

2021. 8. 19. 21:32

📚 0. 들어가면서

이전 포스팅에서 의존관계 주입(DI)가 왜 필요한가에 대해 예제 코드를 가지고 이야기해보았습니다.

 

궁극적으로 DI는 객체지향 설계의 원칙인 DIP와 OCP를 지키기 위한 노력에서 탄생했다는 것을 알 수 있었습니다.

 

이번 시간에는 예전에 자바로 작성했던 AppConfig를 스프링을 이용해 리팩토링해보고, 이 때 사용되는 스프링 컨테이너와 빈(Bean)의 개념에 대해 공부해보려고 합니다.

 

 

 

💻 1. 예제 코드

우선 아래처럼 자바 기반으로 작성한 코드가 있다고 해봅시다.

 

MemberRepository는 멤버를 저장하는 데이터베이스이고 discountPolicy는 어떤 물건을 살 때 적용되는 할인 정책입니다. 그리고 orderService를 통해 멤버는 주문을 하게 됩니다.

 

지금은 AppConfig라는 객체를 통해 의존관계 주입(생성자 주입)을 하고 있습니다.

public class AppConfig {
    public MemberService memberService() {
        return new MemberServiceImpl(memberRepository());
    }

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

    public OrderService orderService(){
        return new OrderServiceImpl(memberRepository(), discountPolicy());
    }

    public DiscountPolicy discountPolicy() {
        return new RateDiscountPolicy();
//        return new FixedDiscountPolicy();
    }
}

 

이를 통해 OrderService의 구현 객체(OrderServiceImpl)는 추상화(MemberRepository, DiscountPolicy 등 인터페이스)에만 의존할 수 있게 되었습니다.

public class OrderServiceImpl implements OrderService{
//    private final DiscountPolicy discountPolicy = new FixDiscountPolicy();    // 고정 할인 정책
//    private final DiscountPolicy discountPolicy = new RateDiscountPolicy();   // 비율 할인 정책

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    // OrderServiceImpl 입장에서 생성자를 통해 어떤 구현 객체가 주입될지는 알 수 없다. - AppConfig에서 결정
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy){
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
    
    ...
    ...
}

public class orderServiceTest {

    MemberService memberService;
    OrderService orderService;

    @BeforeEach
    public void beforeEach(){
        // 의존관계 주입
        AppConfig appConfig = new AppConfig();
        MemberService memberService = appConfig.memberService();
        OrderService orderService = appConfig.OrderService();
    }
    ...
    ...
}

 

이제 위 예시 코드까지 흐름을 이해했다고 가정하고, 지금까지 작성한 자바 기반의 AppConfig를 스프링을 이용해 바꿔보겠습니다.

 

📋 2. 스프링 컨테이너

위에서 작성한 AppConfig를 스프링을 이용해 작성하면 아래와 같이 바꿀 수 있습니다.

 

아래 코드에서 @Configuration과 @Bean을 주목해주세요.

@Configuration
public class AppConfig {
    @Bean
    public MemberService memberService() {
        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 RateDiscountPolicy();
//        return new FixedDiscountPolicy();
    }

 

그리고 이전에 구현했던 OrderServiceImpl을 사용할 때는 아래처럼 사용할 수 있습니다. 아래 코드에서는 ApplicationContext에 대해 주목해주세요.

public class OrderServiceImpl implements OrderService{
    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy){
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
    ...
    ...
}

public class OrderApp {

    public static void main(String[] args) {
        // 의존관계 주입
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

        MemberService memberService = applicationContext.getBean("memberService", MemberService.class);
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);

        ...
        ...
    }
}

(1)에서 써놨던 예제 코드가 스프링 코드로 작성되었을 때 달라진 점은 @Configuration, @Bean, ApplicationContext의 등장입니다.

 

여기서 "ApplicationContext"를 스프링 컨테이너라고 하며, 기존에는 개발자가 AppConfig를 사용해서 직접 객체를 생성하고 DI를 했지만, 이제부터는 스프링 컨테이너의 도움을 받게 됩니다. 

 

스프링 컨테이너는 어노테이션 기반의 @Configuration이 붙은 @AppConfig를 설정 정보로 사용하게 되고, 여기서 @Bean이라 적힌 메서드를 모두 호출해서 반환된 객체를 스프링 컨테이너에 등록합니다.

 

그리고 이렇게 스프링 컨테이너에 등록된 객체를 스프링 빈(Spring Bean)이라고 합니다.

 

즉, new AnnotationConfigApplicationContext(AppConfig.class)를 통해 스프링 컨테이너가 생성이 되고 AppConfig.class를 통해 구성 정보(스프링 빈)를 등록해주게 되는 것입니다. 그림으로 보면 아래와 같습니다.

스프링 컨테이너와 빈

이후에 스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입(DI)합니다.

스프링 컨테이너 DI

 

3. 스프링 컨테이너 확인해보기

 

실제로 등록된 스프링 빈을 확인하는 테스트 코드를 작성해보겠습니다.

 

public class ApplicationContextInfoText {

    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("beanDefinitionName = " + beanDefinitionName + " object = " + bean);
            }
        }
    }
}

getBeanDefinitionNames()는 해당 스프링 컨테이너에 등록된 스프링 빈의 이름을 string 배열 형태로 리턴해주며 getBean(빈 이름, 타입)을 통해 스프링 컨테이너에서 스프링 빈을 조회할 수 있게 해줍니다.

 

위 코드에서 ROLE_APPLICATION으로만 조회했기 때문에 사용자가 직접 등록한 애플리케이션 빈만 결과로 나타나게 되고, 결과는 아래와 같습니다.

 

스프링 빈 조회하기

 

지금까지 스프링 컨테이너와 스프링 빈, 그리고 등록된 스프링 빈을 조회하는 것까지 공부했습니다.

 

다음 포스팅에서 스프링 컨테이너와 스프링 빈을 어떻게 사용하는지, 이게 왜 필요한지에 대해 정리해보겠습니다.

 

4. Reference

- 자바 ORM 표준 JPA 프로그래밍(에이콘 오픈 소스 프로그래밍 시리즈) | 김영한 | 에이콘출판