본문 바로가기
Spring/스프링 핵심 원리 - 기본편

9-1. 빈 스코프: 프로토타입 스코프

by hk27 2022. 1. 12.
스프링 빈은 싱글톤으로만 관리되나요?

 

안녕하세요.

스프링 빈의 스코프가 무엇인지 알아보고, 그중 프로토타입 스코프에 대해서 자세히 살펴보겠습니다.

 

빈 스코프

https://passionate.tistory.com/13에서 싱글톤 컨테이너를 학습하였습니다. 

 

5-1. 싱글톤 컨테이너 + static method

@Configuration은 왜 사용하는 것일까요? 안녕하세요. 오늘은 싱글톤 컨테이너에 대해서 알아보겠습니다. 싱글톤 컨테이너를 학습하고, @Configuration 사용 이유를 이해하는 것이 오늘의 목표입니다.

passionate.tistory.com

스프링 빈은 스프링 컨테이너의 시작과 함께 생성되어서 스프링 컨테이너가 종료될 때까지 유지됨을 알아보았습니다. 

이것은 스프링 빈이 기본적으로 싱글톤 스코프로 생성되기 때문입니다.

스코프(scope)는 '범위'라는 뜻이 있습니다. 번역 그대로 빈 스코프는 빈이 존재할 수 있는 범위를 나타냅니다.

 

스프링은 다음과 같은 다양한 스코프를 지원합니다.

싱글톤: 기본 스코프로, 스프링 컨테이너의 시작부터 종료까지 유지되는 가장 넓은 범위의 스코프입니다.

프로토타입: 스프링 컨테이너가 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는 매우 짧은 범위의 스코프입니다.

웹 관련 스코프: 웹과 관련된 스코프로, request, session, application 등이 있으며 https://passionate.tistory.com/28에서 자세히 알아보았습니다.

 

빈 스코프는 다음과 같이 지정할 수 있습니다.

 

컴포넌트 스캔 자동 등록

@Scope("prototype")
@Component
public class HelloBean {}

수동 등록

@Scope("prototype")
@Bean
PrototypeBean HelloBean() {
   return new HelloBean(); 
}

 

싱글톤 스코프는 앞서 학습하였으므로 이번 시간에는 프로토타입 스코프를 알아보겠습니다. 

 

프로토타입 스코프

프로토타입 스코프의 빈을 스프링 컨테이너에 조회하면 스프링 컨테이너는 항상 새로운 인스턴스를 생성해서 반환합니다. 

프로토타입 빈 요청은 아래 사진과 같은 순서로 동작합니다.

1. 프로토타입 스코프의 빈을 스프링 컨테이너에 요청합니다.

2. 스프링 컨테이너는 이 시점에 프로토타입 빈을 생성하고, 필요한 의존관계를 주입합니다. 

 

3. 스프링 컨테이너는 생성한 프로토타입 빈을 클라이언트에 반환합니다.

4. 이후에 스프링 컨테이너에 같은 요청이 오면 항상 새로운 프로토타입 빈을 생성해서 반환합니다. 

 

스프링 컨테이너는 프로토타입 빈을 생성하고, 의존 관계를 주입하고, 초기화하는 단계까지 처리하고 클라이언트에 반환한 이후로는 빈을 관리하지 않습니다. 프로토타입 빈을 관리할 책임은 클라이언트에 있습니다. @PreDestroy같은 종료 메소드가 호출되지 않고, 종료 메소드를 출력해야 한다면 클라이언트가 직접 처리해야 합니다.

 

프로토타입 스코프 - 싱글톤 빈과 함께 사용 시 문제점

스프링은 일반적으로 싱글톤 빈을 사용하므로, 싱글톤 빈이 프로토타입 빈을 사용하는 경우가 많습니다.

싱클톤 빈인 clinetBean 객체가 프로토타입 빈인 prototypeBean 객체를 의존 관계 주입 받는 경우를 생각해봅시다.

아래 사진의 경우입니다. 

clientBean 객체는 싱글톤이므로 당연히 1개만 생성될 것입니다. 그렇다면 PrototypeBean 객체는 어떨까요?

clientBean 객체가 생성된 다음, 의존 관계를 주입받기 위해서 PrototypeBean을 요청하고, 스프링 컨테이너는 PrototypeBean 객체를 생성하고 반환합니다. 이후로는 주입받은 PrototypeBean 1개를 사용하죠. 

즉 프로토타입 스코프인 PrototypeBean객체가 1개만 생성되고 사용되는 것입니다.

클라이언트가 clientBean 내의 PrototypeBean을 여러 번 호출해도, 같은 clientBean에 주입된 PrototypeBean 객체 1개가 지속해서 호출됩니다.

 

그러나 스코프를 프로토타입으로 지정했다면, 이런 상황을 원한 것이 아니겠죠.

클라이언트는 PrototypeBean을 사용할 때마다 새로운 빈이 생성되기를 원할 것입니다.

이러한 문제는 어떻게 해결할 수 있을까요?

PrototypeBean 객체를 의존관계 주입 받지 않고, 필요할 때마다 스프링 컨테이너에 조회해서 새로 생성하면 됩니다.

 

DL(Dependency Lookup), 의존관계 조회(탐색)

필요할 때마다 PrototypeBean 객체를 조회해서 사용해봅시다.

아래 코드는 싱글톤 빈인 ClientBean의 logic이 불리면 스프링 빈 컨테이너에 PrototypeBean을 조회해서 수를 더하는 코드입니다. 

public class PrototypeProviderTest {
    @Test
    void providerTest() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
        ClientBean clientBean1 = ac.getBean(ClientBean.class);
        int count1 = clientBean1.logic();
        assertThat(count1).isEqualTo(1);
        ClientBean clientBean2 = ac.getBean(ClientBean.class);
        int count2 = clientBean2.logic();
        assertThat(count2).isEqualTo(1);
    }

    static class ClientBean {
        @Autowired
        private ApplicationContext ac;
        public int logic() {
            PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
            prototypeBean.addCount();
            int count = prototypeBean.getCount();
            return count;
        }
    }

    @Scope("prototype")
    static class PrototypeBean {
        private int count = 0;
        public void addCount() {
            count++;
        }
        public int getCount() {
            return count;
        }
    }
}

이렇게 하면 프로토타입 빈으로 동작할 것입니다. 

의존관계를 외부에서 주입(Dependency Injection) 받는 게 아니라 이렇게 직접 필요한 의존 관계를 찾는 것을 의존 관계 조회(탐색), 영어로는 Dependency Lookup(DL)이라고 합니다.

그런데 위의 코드처럼 스프링의 애플리케이션 컨텍스트를 주입 받아서 사용하면 스프링 컨테이너에 종속적인 코드가 되고, 단위 테스트도 어려워집니다.

애플리케이션 컨텍스트는 그리고 너무 헤비합니다. DL 말고도 여러 기능이 있죠.

지금은 PrototypeBean을 스프링 컨테이너에서 대신 조회해주는 DL 기능만 제공하는 것이 필요합니다.

그것이 바로 ObjectProvider입니다.

 

ObjectProvider

지정한 빈을 컨테이너에서 대신 찾아주는 DL 서비스를 제공하는 것이 ObjectProvider입니다.

@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
public int logic() {
        PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
        prototypeBean.addCount();
        int count = prototypeBean.getCount();
        return count;
}

ObjectProvider는 이름 그대로 객체를 제공하는 역할을 합니다. 제너릭 타입을 원하는 객체의 타입으로 넣어서 사용합니다. 객체를 받아올 때는 provider.getObject();로 받아올 수 있습니다.

ObjectProvider는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환하는 DL 기능을 제공합니다.

스프링이 제공하는 기능을 사용하지만, 기능이 단순하여 단위테스트를 만들기 용이합니다. 

 

 

 

인프런  '스프링 핵심 원리 - 기본편' 강의를 듣고 공부하며 정리한 자료입니다. 

잘못된 부분은 피드백 주시면 감사하겠습니다. 

글 읽어주셔서 감사합니다 :-)

 

참고자료

[1] 스프링 핵심 원리 - 기본편, 섹션 9. 빈 스코프 https://www.inflearn.com/course/스프링-핵심-원리-기본편

 

댓글