~/tech-blog/posts/spring-core-provider — zsh
지금 --명이 보고 있습니다오늘 --누적 --

프로토타입과 싱글톤을 함께 쓸 때 Provider로 푸는 법

이전에 프로토타입 스코프와 싱글톤 빈에 대해 작성하였습니다. 그 글에서는 싱글톤 빈이 프로토타입 빈을 주입받으면 주입 시점의 프로토타입 빈 하나를 계속 사용하게 되는 문제를 다뤘습니다. 이번에는 이 문제를 ObjectFactory, ObjectProvider로 해결하는 방법에 대해 정리하도록 하겠습니다.

ObjectFactory, ObjectProvider

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

지정한 빈을 컨테이너에서 대신 찾아 주는 DL 서비스를 제공하는 것이 ObjectProvider입니다. 참고로 과거에는 ObjectFactory가 있었는데, 여기에 편의 기능을 추가해서 ObjectProvider가 만들어졌습니다.

ObjectProvider를 사용하도록 SingletonWithPrototypeTest1을 바꾼 코드는 아래와 같습니다.

Java
package hello.core.scope;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import static org.assertj.core.api.Assertions.assertThat;

public class SingletonWithPrototypeTest1 {

    @Test
    void singletonClientUsePrototype() {
        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 ObjectProvider<PrototypeBean> prototypeBeanProvider;
        public int logic() {
            PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
            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;
        }
        @PostConstruct
        public void init() {
            System.out.println("PrototypeBean.init " + this);
        }
        @PreDestroy
        public void destroy() {
            System.out.println("PrototypeBean.destroy");
        }
    }

}

실행해 보면 prototypeBeanProvider.getObject()를 통해서 항상 새로운 프로토타입 빈이 생성되는 것을 확인할 수 있습니다. 이전 글의 주입 방식과 비교하면 아래와 같습니다.

주입받은 프로토타입 빈을 계속 쓰면 count가 1, 2가 되고, ObjectProvider.getObject()로 호출마다 조회하면 새 빈이 생성되어 count가 1, 1이 되는 비교눌러서 원본 크기로 보기

주입 방식에서는 클라이언트 A와 B가 같은 프로토타입 빈을 사용하지만, ObjectProvider를 사용하면 logic()을 호출할 때마다 컨테이너에서 새 프로토타입 빈을 받아 오므로 두 번 모두 count가 1이 됩니다.

  • ObjectProvider의 getObject()를 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환합니다(DL).
  • 스프링이 제공하는 기능을 사용하지만, 기능이 단순하므로 단위 테스트를 만들거나 mock 코드를 만들기가 훨씬 쉬워집니다.
  • ObjectProvider는 지금 필요한 DL 정도의 기능만 제공합니다.

두 인터페이스의 특징은 아래와 같습니다.

  • ObjectFactory : 기능이 단순, 별도의 라이브러리 필요 없음, 스프링에 의존
  • ObjectProvider : ObjectFactory 상속, 옵션, 스트림 처리 등 편의 기능이 많고, 별도의 라이브러리 필요 없음, 스프링에 의존

마무리

오늘은 싱글톤 빈과 프로토타입 빈을 함께 사용할 때 생기는 문제를 ObjectProvider로 해결하는 방법에 대해 정리하였습니다.

싱글톤 빈과 함께 사용할 때는 ObjectProvider를 사용하는 것을 권장합니다. 다만 스프링이 아닌 다른 DI 컨테이너에서도 동작해야 한다면 자바 표준인 javax.inject.Provider를 사용해야 하는데, 거의 사용할 일이 없을 것 같아 이런 것이 있다는 정도만 알고 넘어가려고 합니다. 무엇보다도 싱글톤과 함께 사용되는 프로토타입 빈이라면 ObjectProvider.getObject()를 통해서 가져와야 한다는 것을 기억해 두면 좋을 것 같습니다.

다음 글에서는 웹 스코프에 대해 정리하도록 하겠습니다.

이 글은 인프런 김영한 님의 스프링 핵심 원리 — 기본편을 들으며 정리한 노트입니다.

댓글

GitHub 계정으로 로그인하시면 댓글과 질문을 남기실 수 있습니다. 남겨 주신 글은 이 저장소의 Discussions에 쌓입니다.

● main 231 posts UTF-8 LF © 2026 코징 RSS · GitHub