ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Spring Boot] 빈 스코프 범위 (싱글톤, 프로토타입, 웹)
    Back-End/Spring Boot 2022. 10. 6. 11:01

     

     

    📑 빈 스코프란?

     

    앞서 살펴본 포스팅에서 스프링 빈이 스프링 컨이너의 시작과 함께 생성되어 스프링 컨테이너가 종료될 때까지 유지된다고 알아보았다. 이것은 스프링 빈이 기본적으로 싱글톤 스코프로 생성되기 때문이다. 여기서) 스코프는 빈이 존재할 수 있는 범위를 의미한다. 

     

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

     

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

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

    📌 웹 관련 스코프

    request
    : HTTP 요청 하나가 들어오고 나갈 때 까지 유지되는 스코프로, 각각의 HTTP 요청마다 별도의 빈 인스턴스가 생성되고 관리된다. 
    ex) 하나의 요청에 의해 호출된 페이지와 포워드(요청전달)된 페이지까지 공유

    session
    : HTTP Session과 동일한 생명주기를 가지는 스코프
    ex) 클라이언트가 처음 접속한 후 웹 브라우저를 닫을 때까지 공유

    application
    : 서블릿 컨텍스트(servletContext)와 동일한 생명주기를 가지는 스코프
    ex) 한 번 저장된 웹 애플리케이션이 종료될 때까지 유지

     

     

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

     

     

     

     

    📑 싱글톤 빈 스코프

     

     

     

    package hello.core.scope;
    
    import org.assertj.core.api.Assertions;
    import org.junit.jupiter.api.Test;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Scope;
    
    import javax.annotation.PostConstruct;
    import javax.annotation.PreDestroy;
    
    public class TestExercise {
    
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SingletonBean.class);
    
        @Test
        void singletonTest(){
            SingletonBean bean1 = ac.getBean(SingletonBean.class);
            System.out.println("bean1 = " + bean1);
    
            SingletonBean bean2 = ac.getBean(SingletonBean.class);
            System.out.println("bean2 = " + bean2);
    
            Assertions.assertThat(bean1).isSameAs(bean2);
    
            ac.close();
    
    
        }
    
        
        @Scope
        static class SingletonBean{
    
            @PostConstruct
            public void init(){
                System.out.println("SingletonBean.init");
            }
    
            @PreDestroy
            public void close(){
                System.out.println("SingletonBean.close");
            }
    
        }
    }

     

     

    테스트 결과를 살펴보면 클라이언트가 싱글톤 스프링 빈을 스프링 컨테이너에 요청하면 빈 초기화 메서드 실행 후, 같은 인스턴스가 있다면 생성되어 있는 빈을 반환한다. 마지막 ac.close()를 통해 종료 메서드까지 정상 호출된 걸 확인할 수 있다.

     

     

     

     


     

     

     

    📑 프로토타입 빈 스코프

     

     

    @Scope("prototype") 으로 PrototypeBean 클래스를 생성한 후 테스트를 진행해보면 아래와 같은 결과가 나온다.

     

     

     

    싱글톤 빈은 스프링 컨테이너 생성 시점에 초기화 메서드가 실행되지만, 프로토타입 빈은 스프링 컨테이너에서 빈을 조회할 때 생성되고, 초기화 메서드도 실행된다. 살펴보면 2번 조회되는 걸 확인할 수 있으며 객체도 서로 다른 객체인 걸 확인할 수 있다. 

     

    스프링 컨테이너가 프로토타입 빈을 생성하면 의존관계 주입과 초기화까지만 처리하고 그 다음 책임은 클라이언트에게 맡긴다. 따라서 @PreDestroy같은 종료 메서드는 호출되지 않는다. 

     

     

     

    📑 싱글톤에서 프로토타입 빈 사용하기

     

    ClientBean이라는 싱글톤 빈이 의존관계 주입을 통해 프로토타입 빈을 주입받아서 사용하는 예제를 한 번 살펴보자.

     

     

    @Test
        void SingletonWithPrototypeTest(){
            AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean1.class, PrototypeBean1.class);
    
            ClientBean1 clientbean = ac.getBean(ClientBean1.class);
            int count1 = clientbean.logic();
            System.out.println("client bean = " + clientbean);
            System.out.println("count1 = " + count1);
            assertThat(count1).isEqualTo(1);
    
            ClientBean1 clientbean2 = ac.getBean(ClientBean1.class);
            int count2 = clientbean2.logic();
            System.out.println("client bean2 = " + clientbean2);
            System.out.println("count2 = " + count2);
            assertThat(count2).isEqualTo(2);
    
            
    
            ac.close();
    
        }
    
    
        static class ClientBean1 {
            private final PrototypeBean1 prototypeBean1;
    
            ClientBean1(PrototypeBean1 prototypeBean1) {
                this.prototypeBean1 = prototypeBean1;
            }
    
            public int logic(){
                prototypeBean1.add();
                int count = prototypeBean1.getCount();
                return count;
            }
    
    
        }
    
    
    
        @Scope("prototype")
        static class PrototypeBean1 {
    
            private int count = 0;
    
            public void add(){
                count++;
            }
    
            public int getCount() {
                return count;
            }
    
            @PostConstruct
            public void init(){
                System.out.println("PrototypeBean.init");
            }
    
            @PreDestroy
            public void close(){
                System.out.println("PrototypeBean.close");
            }
    
        }

     

     

    📌 1. ClientBean은 싱글톤이므로, 스프링 컨테이너 생성 시점에 함께 생성되고, 의존관계 주입도 발생한다. 따라서 의존관계 자동 주입을 통해(생성자 주입) 스프링 컨테이너에 프로토타입 빈을 요청한다. 

     

    📌 2. 스프링 컨테이너는 프로토타입 빈을 생성해서 clientBean에 반환하는데 이때 count 필드의 값은 0이다.  이때부터 프로토타입 빈은 clietntBean에서 관리하게 되는 것.

     

     

     


     

     

    📌  클라이언트 A는 clientBean을 스프링 컨테이너에 요청해서 받는다.  ClientBean1 clientbean = ac.getBean(ClientBean1.class);

     

    📌 클라이언트 A는 clientBean.logic()을 호출한다. clientBean은 prototypeBean의 addCount()를 호출해서 count를 증가한다. 값은 1이 된다. 

     

     


     

    📌  클라이언트B는 clientBean을 스프링 컨테이너에 요청해서 받는다. clientBean은 싱글톤이므로 항상 같은 clientBean을 반환한다. 즉) 클라이언트A에게 반환했던 것과 같은 clientBean을 반환

     

    📌 클라이언트 B는 clientBean.logic()을 호출한다. 여기서 prototypeBean을 사용하게 되는데, 이 프로토타입빈은 이미 clientBean 내부에 가지고 있는 것, 즉) 주입이 끝난 빈이다. 새로 생성되는 것이 아니다. 

     

    📌 따라서 clientBean은 prototypeBean에 add()를 호출해 프로토타입 빈의 count를 증가시킨다. 원래 1이었으므로 2가된다. 

     

     

     


     

    🔨 스프링은 일반적으로 싱글톤 빈을 사용하므로, 싱글톤 빈이 프로토타입 빈을 사용하게 된다.

    그런데 싱글톤 빈은 생성 시점에만 의존관계 주입을 받기 때문에, 프로토타입 빈이 새로 생성되기는 하지만, 싱글톤 빈과 함께 계속 유지되는 것이 문제다.

     

     

    하지만, 의도는 프로토타입빈을 주입 시점에만 사용하는 것이 아니라 사용할 때 마다 새로 생성하여 사용하고 싶다.!  그렇다면 어떻게 해야될까? 

     

     

    📑 스프링 컨테이너에 요청 - ObjectProvider

     

    ClientBean에서 스프링 컨테이너에 항상 새로운 프로토타입  빈을 생성해서 받아오면된다. 의존관계를 외부에서 주입(DI)받는 것이 아니라 직접 필요한 의존관계를 찾는 것을 Dependency Lookup(DL) 의존관계 조회(탐색)이라고 한다. 

     

    스프링에는 지정한 빈을 컨테이너에서 대신 찾아주는 DL 서비스 기능이  있는데, 그게 바로 ObjectProvider이다. 

     

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

     

    ObjectProvider의 getObject()를 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다.!  그렇게 때문에 클라이언트가 clientBean호출 시 미리 만들어져있던 프로토타입빈을 통해 값을 반환하는 것이 아닌, 새롭게 호출해서 다른 프로토타입빈을 생성하는 것! 

     

     

     

     

     

     

     

    반응형

    댓글

Designed by Tistory.