코딩성장스토리

스프링 빈 스코프 본문

백 엔드/spring

스프링 빈 스코프

까르르꿍꿍 2022. 2. 20. 16:23

싱글톤 타입과 프로토타입 차이점

싱글톤: 기본 스코프, 스프링 컨테이너의 시작과 종료까지 유지되는 가장 넓은 범위의 스코프이다.→항상 같은 인스턴스의 스프링 빈을 반환
프로토타입: 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더는 관리하지 않는
매우 짧은 범위의 스코프이다. →스프링 컨테이너에 조회하면 스프링 컨테이너는 항상 새로운 인스턴스를 생성후 조회

웹 관련 스코프

request: 웹 요청이 들어오고 나갈때 까지 유지되는 스코프이다.
session: 웹 세션이 생성되고 종료될 때 까지 유지되는 스코프이다.
application: 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프이다.

 

싱글톤 스코프 일때

public class SingleTonTest {

    @Test
    void singletonBeanFind(){
        AnnotationConfigApplicationContext ac= new AnnotationConfigApplicationContext(SingletonBean.class);
        SingletonBean singletonBean1 = ac.getBean(SingletonBean.class);
        SingletonBean singletonBean2 = ac.getBean(SingletonBean.class);
        System.out.println("singletonBean1 = " + singletonBean1);
        System.out.println("singletonBean2 = " + singletonBean2);
        assertThat(singletonBean1).isSameAs(singletonBean2);
        ac.close(); //종료
    }
    @Scope("singleton")
    static class SingletonBean{
        @PostConstruct
        public void init(){
            System.out.println("SingletonBean.init");
        }

        @PreDestroy
        public void destroy(){
            System.out.println("SingletonBean.destroy");
        }
    }
}


출력값

SingletonBean.init
singletonBean1 = hello.core.scope.SingleTonTest$SingletonBean@66c92293
singletonBean2 = hello.core.scope.SingleTonTest$SingletonBean@66c92293
15:00:36.704 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@1b11171f, started on Sun Feb 20 15:00:36 KST 2022
SingletonBean.destroy

프로토 타입 일때

public class PrototypeTest {
    @Test
    public void prototypeBeanFind() {
        AnnotationConfigApplicationContext ac = new
                AnnotationConfigApplicationContext(PrototypeBean.class);
        System.out.println("find prototypeBean1");
        PrototypeBean prototypeBean1 = ac.getBean(PrototypeBean.class);
        System.out.println("find prototypeBean2");
        PrototypeBean prototypeBean2 = ac.getBean(PrototypeBean.class);
        System.out.println("prototypeBean1 = " + prototypeBean1);
        System.out.println("prototypeBean2 = " + prototypeBean2);
        assertThat(prototypeBean1).isNotSameAs(prototypeBean2);
        ac.close(); //종료
    }
    @Scope("prototype")
    static class PrototypeBean {
        @PostConstruct
        public void init() {
            System.out.println("PrototypeBean.init");
        }
        @PreDestroy
        public void destroy() {
            System.out.println("PrototypeBean.destroy");
        }
    }
}

출력값

find prototypeBean1
PrototypeBean.init
find prototypeBean2
PrototypeBean.init
prototypeBean1 = hello.core.scope.PrototypeTest$PrototypeBean@332796d3
prototypeBean2 = hello.core.scope.PrototypeTest$PrototypeBean@4f0100a7

 

위의 싱글톤과 프로토 타입의 결과들을 보면 

싱글톤 빈은 스프링 컨테이너가 관리하기 때문에 스프링 컨테이너가 종료될 때 빈의 종료 메서드가
실행되지만, 프로토타입 빈은 스프링 컨테이너가 생성과 의존관계 주입 그리고 초기화 까지만 관여하고,
더는 관리하지 않는다. 따라서 프로토타입 빈은 스프링 컨테이너가 종료될 때 @PreDestroy 같은 종료
메서드가 전혀 실행되지 않는다. 또한 프로토타입 빈을 2번 조회했으므로 완전히 다른 스프링 빈이 생성되고, 초기화도 2번 실행된 것을 확인할 수 있다.

 

싱글톤에서 프로토타입을 사용할 경우 주의점

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(2);
    }
    static class ClientBean {
        private final PrototypeBean prototypeBean;
        @Autowired
        public ClientBean(PrototypeBean prototypeBean) {
            this.prototypeBean = prototypeBean;
        }
        public int logic() {
            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");
        }
    }
}

싱글톤에서 프로토 타입을 받아도 이미 싱글톤 안에 프로토타입 객체가 있기 때문에 클라이언트가 요구해도 생성된 프로토타입 객체를 사용한다. (즉, 프로토타입을 써도 다른 객체 생성이 안된다.)

이럴 경우 무식한 해결 방법으로는 싱글톤 안에서 프로토 타입을 계속 생성해 주는 것이다.

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

이 코드의 문제점은 직접 의존관계를 찾는 (Dependency Lookup(DL))이 된다. 이렇게 되면은 스프링 컨테이너에 종속적이게 되므로 좋은 코드가 아니다.

 

이럴 때 쓰는 것이 ObjectFactory, ObjectProvider


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

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

이러면 객체를 새로 가져다 준다.