ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring 기본 36 - 프로토타입 스코프(싱글톤 빈과 함께 사용시 Provider로 문제 해결)
    FrameWork/Spring&Spring-boot 2023. 11. 30. 11:26

    프로토타입 스코프(싱글톤 빈과 함께 사용시 Provider로 문제 해결)

    싱글톤 빈과 프로토타입 빈을 함께 사용할 때, 어떻게 하면 사용할 때 마다 항상 새로운 프로토타입 빈을 생성할 수 있을까?

     

    스프링 컨테이너에 요청

    가장 간단한 방법은 싱글톤 빈이 프로토타입을 사용할 때 마다 스프링 컨테이너에 새로 요청하는 것이다.

    package com.hello.core.scope;
    
    import lombok.RequiredArgsConstructor;
    import org.assertj.core.api.Assertions;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationContext;
    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.*;
    
    public class SingletonWithPrototypeTest1 {
    
        @Test
        void prototypeFind(){
            AnnotationConfigApplicationContext ac =
                    new AnnotationConfigApplicationContext(PrototypeBean.class);
    
            PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class);
            prototypeBean1.addCount();
    
            assertThat(prototypeBean1.getCount()).isEqualTo(1);
    
            PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class);
            prototypeBean2.addCount();
    
            assertThat(prototypeBean2.getCount()).isEqualTo(1);
        }
    
        @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);
    
        }
    
    
        @Scope("singleton")
        static class ClientBean{
            //생성시점에 주입
            private  ApplicationContext ac;
    
            public ClientBean(ApplicationContext ac, PrototypeBean prototypeBean) {
                this.ac = ac;
            }
    
            public int logic(){
                PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
                prototypeBean.addCount();
                return prototypeBean.getCount();
            }
        }
    
        @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");
            }
        }
    }
    

     

    핵심코드

    @Scope("singleton")
    static class ClientBean{
        private  ApplicationContext ac;
    
        public ClientBean(ApplicationContext ac, PrototypeBean prototypeBean) {
            this.ac = ac;
        }
    
        public int logic(){
            PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
            prototypeBean.addCount();
            return prototypeBean.getCount();
        }
    }

     

    • 실행해보면 `ac.getBean()`을 통해서 항상 새로운 프로토타입 빈이 생성되는 것을 확인할 수 있다.
    • 의존관계를 외부에서 주입(DI)받는게 아니라 이렇게 직접 필요한 의존관계를 찾는 것을 Dependency Lookup(DL) 의존관계 조회(탐색)이라고 한다.
    • 그런데 이렇게 스프링 애플리테이션 컨텍스트 전체를 주입받게 되며, 스프링 컨테이너에 너무 종속적이 코드가 되고, 단위 테스트도 어려워진다.
    • 지금 필요한 기능은 지정한 프로토타입 빈을 컨테이너에서 대신 찾아주는 딱! DL 정도의 기능남 제공하는 무언가가 있으면 된다.

     

    ObjectFactory, ObjectProvider

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

    static class ClientBean{
    
        @Autowired
        private ObjectProvider<PrototypeBean> prototypeBeanProvider;
    
        public int logic(){
            PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
            prototypeBean.addCount();
            return prototypeBean.getCount();
        }
    }
    • 실행해보면 `prototypeBeanProvider.getObject()`을 통해서 항상 새로운 프로토타입 빈이 생성되는 것을 확인할 수 있다.
    • `ObjectProvider`의 `getObject()`를 호출하면 내부에서 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다.(*DL*)
    • 스프링이 제공하는 기능을 사용하지만, 기능이 단순하므로 단위테스트를 만들거나 mock 코드를 만들기는 훨씬 쉬어진다.
    • `ObjectProvider`는 딱 필요한 DL정도의 기능만 제공한다.

     

    특징

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

     

    JSR-330 Provider

    마지막 방법은 `javax.inject.Provider`라는 JSR-330자바 표준을 사용하는 방법이다.

    이 방법을 사용하려면`javax.inject:javax.inject:1` 라이브러리를 gradle에 추가 해야한다.

    package javax.inject;
    public interface Provider<T> {
        T get();
    }

     

    @Scope("singleton")
    static class ClientBean{
    
        @Autowired
        private Provider<PrototypeBean> prototypeBeanProvider;
    
        public int logic(){
            PrototypeBean prototypeBean = prototypeBeanProvider.get();
            prototypeBean.addCount();
            return prototypeBean.getCount();
        }
    }
    • 실행해보면 `provider.get()`을 통해서 항상 새로운 프로토타입 빈이 생성되는 것을 확인할 수 있다.
    • `provider`의 `get()`을 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다.(*DL*)
    • 자바 표준이고, 기능이 단순하므로 단위테스트를 만들거나 mock코드를 만들기는 훨씬 쉬워진다.
    • `Provider`는 딱 필요한 DL 정도의 기능만 제공한다.

     

    특징

    • `get()`메서드 하나로 기능이 매우 단순하다.
    • 별도의 라이브러리가 필요하다.
    • 자바 표준으로 스프링이 아닌 다른 컨테이너에서도 사용할 수 있다.

     

    정리

    • 그러면 프로토타입 빈을 언제 사용할까? 매번 사용할 때 마다 의존관계 주입이 완료된 새로운 객체가 필요하면 사용하면 된다. 그런데 실무에서 웹 어플리케이션을 개발해보면, 싱글톤 빈으로 대부분의 문제를 해결할 수 있기 때문에 프로토타입 빈을 직접적으로 사용하는 일은 매우 드물다.
      • 지연(lazy)해서 가져오거나 optional로 객체 인스턴스를 가져 올 때
      •  A가 B를 의존하고 B가A를 의존할 때 (순환참조,breaking circular dependencies)
    • `ObjectProvider`,`JSR330 Provider`등은 프로토타입 뿐만 아니라 DL이 핑요한 경우는 언제든지 사용할 수 있다.

     

     

    참고
    스프링이 제공하는 메서드에`@Lookup`애노테이션을 사용하는 방법도 있지만, 고려해야 할 사항이 많다.

     

     

     

    [출처 - 스프링 핵심 원리 - 기본편]

    https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8

     

    스프링 핵심 원리 - 기본편 - 인프런 | 강의

    스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보세요! 📢

    www.inflearn.com

     

    댓글

Designed by Tistory.