티스토리 뷰

SPRING

프로토타입 스코프

짜비 2021. 5. 20. 23:50

앞서서 싱글톤 패턴에 대해 공부했다.

https://flowingmooon.tistory.com/20?category=996069 

 

싱글톤 컨테이너 (1) 등장배경, 정의, 주의사항

등장배경 스프링 없이 순수 java 코드로 짜면, DI 컨테이너(이전 게시물 참고)는 클라이언트가 요청을 보낼 때마다 새로운 객체를 만들어서 반환한다. 가령 고객의 트래픽이 초당 100만큼 발생하면

flowingmooon.tistory.com

 

싱글톤 패턴에 따르면, 스프링 빈은 스프링 컨테이너가 생성될 때 등록되고, 스프링 컨테이너가 종료될 때까지 유지된다.

이를 두고 스프링 빈이 싱글톤 스코프를 따른다고 한다. 그렇다면 스프링 빈에 다른 스코프를 부여할 수 있을까?

 

@Scope 어노테이션을 활용하면 된다. 이번 포스팅에서는 싱글톤 스코프와는 다른 프로토타입 스코프에 대해 알아보자.

 

프로토타입으로 빈을 등록해두면, 스프링 컨테이너는 해당 빈이 조회되었을 때 비로소 빈을 생성하고, 의존관계를 주입한다. 그리고 이를 반환하고 해당 빈을 따로 관리하지 않는다.

 

    @Test
    public 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);
    }
    
    @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");
        }
    }

 

위 테스트 코드를 실행해보면

서로 다른 인스턴스가 생성된 것을 확인할 수 있다. 즉, 조회될 때마다 새로운 빈이 생성되는 것이다.

또한 @PreDestroy가 붙은 detroy( ) 메소드가 호출되지 않았는데, 이를 통해 스프링 컨테이너가 프로토타입 빈에 대해서는 생성-의존관계 주입-초기화-전달 이후에는 관리를 하지 않는다는 걸 볼 수 있다.

 

정리하자면, 프로토타입 스코프는 싱글톤 스코프와 달리 빈이 조회되었을 때 생성되며, 스프링 컨테이너는 빈 조회 요청을 끝내고 나면 빈을 따로 관리하지 않는다. 

 

어렵지 않은 개념인데, 싱글톤 스코프와 프로토타입 스코프를 함께 사용하면 문제가 생긴다.

다음과 같은 상황을 생각해보자.

싱글톤 빈(clientBean)에서 프로토타입 빈을 주입받고, 클라이언트들은 싱글톤 빈을 통해 logic을 수행한다.

그러면 싱글톤 빈은 자신이 생성될 때 프로토타입 빈을 주입 받는다. 이때 프로토타입 빈을 최초 한 번만 주입받는다. 다시 말해 클라이언트들이 싱글톤 빈 내에 있는 프로토타입 빈을 조회하더라도 프로토타입 빈은 새로 생성되지 않고 원래 값을 유지한다. 그래서 프로토타입 빈 내에 있는 count 값이 계속 증가하게 되는 것이다.

 

이는 프로토타입의 성질과 맞지 않고, 개발자의 의도에도 부합하지 않는 결과이다.

어떻게 하면 프로토타입 빈을 조회할 때마다 새로 생성하게 할 수 있을까?

 

해결 방법은 ObjectProvider 혹은 Provider를 이용하는 것이다.

 

ObjectProvider

특정 빈을 스프링 컨테이너에서 찾아주는 서비스를 제공하는 객체.

(이렇게 의존관계를 외부에서 주입받지 않고 직접 찾는 것을 Dependency Lookup, DL이라고 한다.)

 

       @Scope("singleton")
       static class ClientBean {
       
        @Autowired
        private ObjectProvider<PrototypeBean> prototypeBeanObjectProvider;

        public int logic() {
            PrototypeBean prototypeBean = prototypeBeanObjectProvider.getObject();
            prototypeBean.addCount();
            int count = prototypeBean.getCount();
            return count;
        }

위와 같이 ObjectProvider를 만들고, getObject() method를 통해 빈을 찾을 수 있다.

ObjectProvider가 컨테이너에 프로토타입 빈을 요청하고 이때마다 프로토타입 빈이 생성된다. 프로토타입 빈의 성질에 맞게 로직을 구현한 셈이다!

 

Provider

Provider를 사용하려면 우선 build.gradle dependencies에 'javax.inject:javax.inject:1'을 추가해야 한다.

       @Scope("singleton")
       static class ClientBean {
       
        @Autowired
        private Provider<PrototypeBean> prototypeBeanObjectProvider;

        public int logic() {
            PrototypeBean prototypeBean = prototypeBeanObjectProvider.get();
            prototypeBean.addCount();
            int count = prototypeBean.getCount();
            return count;
        }

코드는 ObjectProvider와 매우 유사하다. getObject() method를 get()으로 바꿔주면 된다.

 

 

참고 : ObjectProvider vs Provider

ObjectProvider는 spring에서, Provider는 java 표준에서 제공한다. 일반적으로 비슷한 기능을 양쪽 모두에서 지원할 때,스프링에서 제공하는 기능이 편리한 경우가 많다. 한편, java 표준에서 제공하는 것은 스프링이 아닌 다른 컨테이너에서도 사용할 수 있다는 장점이 있다. 따라서 다른 컨테이너와의 호환성을 생각한다면 Provider를, 그렇지 않다면 dependency를 추가할 필요가 없는 ObjectProvider를 사용하자. 

 

 

사진, 내용 출처 : 인프런, 김영한 강사님의 '스프링 핵심 원리 - 기본편' 강좌, 강의자료

https://inf.run/TErf

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함