백엔드 공부

[스프링 핵심] 스프링 컨테이너

영재임재영 2023. 1. 3. 14:55

 

미리보기

✅ 스프링 컨테이너에 대해 알아보자! (with IoC, DI)

✅ ApplicationContext를 통해 스프링 컨테이너를 들여다보자!


🍩 스프링 컨테이너 그리고 IoC, DI 개념

기존에는 클라이언트 구현 객체직접 프로그램의 제어 흐름을 컨트롤했다. 무슨 말이냐면!

('구현 객체'는 '구현체 클래스'라고 생각하면 된다!)

위의 예시는 티켓 예매와 관련된 프로그램을 만들고 싶어서, 다음과 같은 클래스 다이어그램을 짜봤다.

ReservationService 클래스에서는 TicketReserveImpl 클래스 객체를 인스턴스로 생성할 수 있고, 이를 통해 기능을 구현할 수 있다.

 

방금 뭐라고? ReservationService 클래스(=클라이언트)직접 객체를 만들어 기능을 구현(=프로그램 제어 흐름 컨트롤)했다고!

(당연히 직접 만든다는 것은.. 로봇처럼 알아서 만드는게 아니라 개발자가 코드를 타이핑했다는 것이지! ㅎㅎ)

예상해보면, TicketReserve ticketReserve = new TicketReserveImpl( ); 이런 식으로 객체를 만들었겠지요?

 

하지만 '제어의 역전(Inversion of Control)' 이라는 개념에서 구현 객체는 오직 자신의 로직을 실행하는 역할만 담당하게 된다!

그럼 프로그램의 제어 흐름은 누가 담당하는데? 해당 역할을 담당하는게 바로 '스프링 컨테이너' 라는 것이다.

 

[스프링 입문] 포스팅에서 '스프링 IoC 컨테이너' 라는 단어를 사용했는데, 스프링 컨테이너 자체가 IoC 개념을 갖고 있는 것이다!

그렇다면 스프링 컨테이너가 어떤 방식으로 프로그램의 제어 흐름을 구현하는지 알아보도록 하자!

일단 컨테이너에 클래스들을 등록하는 과정이 필요하다. 그러면 스프링 컨테이너는 이들을 스프링 빈으로 만들어서 등록해놓는다.

'스프링 빈' 은 스프링 컨테이너에 관리되는 '객체' 이다! 그냥 쉽게 '빈 == 객체' 라고 생각하면 된다!

 

컨테이너에 스프링 빈으로 만들어서 등록까지 했다면, 스프링 컨테이너는 위와 같은 작업들을 수행한다.

스프링 빈으로 만들어놓은 구현 객체를 자동으로 생성하고, 이들의 의존관계를 알아서 주입(=Dependency Injection) 해준다!

 

이전과 비교했을 때, 클라이언트 구현 객체였던 ReservationService 클래스의 역할이 줄어들었다.

직접 객체를 생성할 필요 없이, 본인의 코드 로직만 수행하면 되는 것이다! 이들의 의존관계 또한 스프링 컨테이너가 연결해주었다!

 

정리 😆

스프링 프레임워크가 내 코드를 대신 호출해주는 제어의 역전(Inversion of Control)이 발생하였고, (개발자 → 프레임워크)

스프링 컨테이너는 클래스들을 스프링 빈으로 만들어서 관리하고, 알아서 의존관계 주입(=Dependency Injection)을 해준다!

 

☕️ ApplicationContext 통해 스프링 컨테이너 들여다보기

실제 자바 코드에서 스프링 컨테이너를 찾아볼 수 있을까? ㅇㅇ 그렇다. 'ApplicationContext' 라는 것이 있기 때문이다.

ApplicationContext스프링 컨테이너라고 하는데, 이는 다음과 같은 관계를 가지고 있다.

 

BeanFactory는 스프링 컨테이너의 최상위 인터페이스다. 스프링 빈을 관리하고 조회하는 역할을 한다. (메인 기능 → Bean 관련)

이런 BeanFactory의 기능을 상속받아서 제공하는 인터페이스가 바로 스프링 컨테이너, ApplicationContext 이다.

 

BeanFactory의 메인 기능이 Bean과 관련된 것이었다면, ApplicationContext빈 관련 기능 + 편리한 부가 기능을 제공한다.

위 코드는 ApplicationContext 인터페이스 선언 부분인데, 상속받은 여러 인터페이스들을 통해 편리한 부가 기능을 제공받는다.

 

AnnotationConfigApplicationContext에는 Reader가 하나 있는데, 이 Reader는 '설정 정보'를 읽는 역할을 한다.

꽤나 중요한 개념이다! 어떤 설정 정보를 읽어오는데?

 

사실 AnnotationConfigApplicationContext만 있는 것이 아니다. 설정 정보 파일의 유형에 따라 달라진다!

(여기서는 AppConfig.java 파일을 설정 정보로 읽어올 예정이라 AnnotationConfigApplicationContext를 사용한 것이다)

만약 설정 정보 파일이 .xml 파일이라면? 그러면 GenericXmlApplicationContext 라는 것을 사용해 설정 정보를 읽어오는 것이다!

 

그렇다면 설정 정보 파일을 어떤 식으로 작성할 수 있을까! 위의 티켓 예매 프로그램의 클래스 다이어그램을 가져와서 만들어보겠다.

 

ReservationService가 인터페이스고, 이를 구현하는 ReservationServiceImpl 이라는 구현체를 만들었다고 해보자!

그럼 다음과 같이 설정 파일을 작성할 수 있다.

@Configuration
public class AppConfig {
    @Bean
    public ReservationService reservationService() {  // Format : Type BeanName() {}
        return new ReservationServiceImpl(ticketReserve());
    }

    @Bean
    public TicketReserve ticketReserve() {
        return new TicketReserveImpl();
    }
}

일단 @Configuration 어노테이션이 붙으면 스프링 컨테이너에게 "이 클래스가 설정 정보 파일이야!" 라는 것을 알려준다!

또한 스프링 빈으로 등록하려면, @Bean 어노테이션을 통해서 스프링 빈으로 등록할 수 있다.

 

코드를 살펴보면, ReservationService를 리턴 타입으로 가지고 있고, Bean의 이름은 'reservationService' 라고 되어있다.

리턴할 때는 해당 인터페이스의 구현체를 리턴해준다! 즉 ReservationService의 구현체는 ReservationServiceImpl 이라는 것이다!

 

일단 여기까지 봤을 때, 각 인터페이스에 구현 클래스까지 연결되는 것은 알아냈다!

근데 저기 ticketReserve( )를 담아서 리턴하는 이유가 뭐지? ReservationServiceImpl 클래스의 코드를 보자.

public class ReservationServiceImpl implements ReservationService {

    private final TicketReserve ticketReserve;

    public ReservationServiceImpl(TicketReserve ticketReserve) {  // Constructor
      this.ticketReserve = ticketReserve;
    }
}

해당 클래스는 위와 같이 작성할 수 있는데, 생성자 부분에 파라미터로 TicketReserve 타입의 변수를 받는 것을 볼 수 있다.

 

위의 AppConfig 파일에서 ticketReserve( )는 TicketReserveImpl 클래스를 리턴한다는 의미인데,

그러면 ReservationServiceImpl 클래스 내 ticketReserve 변수에는 TicketReserveImpl 클래스가 들어가게 된다.

즉! ReservationServiceImpl 클래스가 TicketReserveImpl 클래스의 인스턴스를 가지고 있고, 이를 사용할 수 있게 된 것이다!

 

'제어의 역전'이 아닌 상황에서는 직접 TicketReserve ticketReserve = new TicketReserveImpl( ); 라고 코드를 썼다면,

이제는 스프링 컨테이너 + 설정 정보 파일을 통해 외부에서 의존관계를 주입받게 되는 것이다! ⭐️⭐️⭐️

 

 

이제 ApplicationContext를 통해 설정 정보 파일을 읽고 스프링 컨테이너에 등록되는 과정을 살펴보자.

ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);

실제로 ApplicationContext를 사용해서 스프링 컨테이너에 Bean이 등록되는걸 살펴보면, 다음과 같다.

[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'appConfig'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'reservationService'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'ticketReserve'

스크롤을 오른쪽으로 옮기면, 어떤 빈들이 등록되었는지 스프링이 찍어준 로그를 볼 수 있다.

위에 있는 5개의 빈은 기본적으로 스프링에서 올려주는 시스템 빈이고, 밑에 3개가 직접 등록한 빈이다.

 

앗! 근데 'appConfig'는 빈으로 등록한 기억이 없는데? ㅇㅇ 맞다. 근데 AppConfig 설정 파일은 알아서 스프링이 빈으로 등록한다!

'reservationService', 'ticketReserve' 라는 이름으로 스프링 빈이 등록된 것을 볼 수 있다!

ReservationService bean1 = ac.getBean(ReservationService.class);
TicketReserve bean2 = ac.getBean(TicketReserve.class);

참고로 스프링 빈은 getBean( ) 메서드를 통해 위와 같이 꺼내올 수 있다! 결과 값을 찍어보면 다음과 같이 나온다!

hello.core.blog.ReservationServiceImpl@3569fc08
hello.core.blog.TicketReserveImpl@20b12f8a

 

스프링 컨테이너가 스프링 빈을 통해 관리해줌으로써, 사용할 수 있는 편리한 기능이 상당히 많다!

추후 포스팅에서는 스프링 컨테이너에게 관리를 맡김으로 인해 얻을 수 있는 이점들에 대해 짚고 넘어갈 것이다! 중요하니까! 😀😀