본문 바로가기
서버/스프링

[Spring] 빈 스코프 prototype

by 베어 그릴스 2022. 8. 28.
320x100

*본 게시글은 김영한님 스프링 핵심 원리 기본편을 보고 이해한 내용을 바탕으로 정리한 글입니다.

 

빈 스코프란?

빈 스코프란 빈이 존재할 수 있는 범위를 뜻한다.

 

스코프를 따로 지정하지 않으면 스프링 빈이 싱클톤으로 생성되어 스프링 빈이 스프링 컨테이너의 시작과 함께 생성되어서 스프링 컨테이너가 종료될 때까지 유지된다.

 

스프링의 3가지 빈 스코프

  • 싱글톤 : 기본 스코프
  • 프로토타입 : 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입까지만 관여하고 더 이상 관리하지 않는 스코프
  • 웹 관련 스코프
    • request: 웹 요청이 들어오고 나갈때 까지 유지되는 스코프이다.
    • session: 웹 세션이 생성되고 종료될 때 까지 유지되는 스코프이다.
    • application: 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프이다.

오늘은 3가지 중 프로토타입 스코프에 대하여 알아보고자 한다,

 

프로토타입 스코프

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


    @Scope("prototype")
    static class PrototypeBean {

    }

위 코드와 같이 빈으로 등록될 클래스에 @Scope("prototype") 어노테이션을 붙여주면 프로토타입 빈으로 인식된다.

 

*위 코드에서는 @Bean이나 @Component를 붙이지 않았지만 스프링 컨테이너에 등록할 때 자동으로 스프링 빈으로 등록된다.

 

위 코드를 실행시킨 결과는 다음과 같다.

싱글톤 빈은 위와 같이 스프링 컨테이너에서 빈을 몇번을 찾아도 같은 인스턴스를 반환하지만,

프로토타입 빈은 스프링 컨테이너는 해당 빈의 생성과 의존관계 주입까지만 관여하므로 똑같은 객체의 스프링 빈을 찾을 때는 스프링이 관리하고 있는 빈이 없으므로 새로 생성하여 생성할 때마다 새로운 빈이 나오게 된다.

 

프로토 타입 빈은 상태를 요청할 때마다 새롭게 생성되어 기능을 수행한다.

 

 

싱글톤 빈과 프로토타입 빈을 같이 사용할 시 문제점

싱글톤 빈이 프로토타입 빈을 의존관계 자동 주입을 받을 때 생기는 문제점이 있다.

 

 

처음 스프링 컨테이너에서 싱글톤 빈에 대한 의존관계 자동 주입을 하고 종료되면,

싱글톤 빈은 내부에 하나의 프로토타입 빈을 가진 채로만 스프링 컨테이너가 종료될 때까지 유지된다.

 

즉, 우리가 하고 싶었던 것은 어떤 요청이 들어올 때 마다 프로토타입 빈이 생성되는 것인데

싱글톤 빈에 의존관계 주입을 통해서 사용하면 마치 싱글톤 빈 처럼 하나의 인스턴스만 계속 유지되어 버리는 것이다.

public class PrototypeTest {


    @Test
    public void prototypeBeanFind() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SingletonBean.class,PrototypeBean.class);
        System.out.println("find SingletonBean1");
        SingletonBean singletonBean1 = ac.getBean(SingletonBean.class);
        System.out.println("find singletonBean2");
        SingletonBean singletonBean2 = ac.getBean(SingletonBean.class);
        singletonBean1.logic();
        singletonBean2.logic();
    }


    @Scope("prototype")
    static class PrototypeBean {

    }


    static class SingletonBean {
        @Autowired
        private PrototypeBean prototypeBean;

        public void logic() {
            System.out.println("prototypeBean = " + prototypeBean);
        }
    }

}

위와 같이 싱글톤 빈 내부에 사용하면 싱글톤 빈 내부에는 같은 인스턴스의 프로토타입 빈만이 유지된다.

 

실행한 결과는 다음과 같다.

 

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

 

 

 

스프링 컨테이너에서 프로토 타입 빈을 받아와서 사용하는 방법

먼저, 그냥 싱글톤 빈 코드에서 의존관계 주입을 받지 않고,

프로토 타입 빈이 사용될 때마다 스프링 컨테이너에서 프로토 타입 빈을 받아와서 사용하는 방법이 있다.

@Scope("prototype")
static class PrototypeBean {

}


static class SingletonBean {
	//스프링 컨테이너 자동 주입
    @Autowired
    private ApplicationContext ac;
        
    public void logic() {
    	//프로토타입 빈이 필요할 때 마다 스프링 컨테이너에서 가져다가 사용하여 계속 새로운 빈이 생성된다
        PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
        System.out.println("prototypeBean = " + prototypeBean);
    }
 }

 

이를 Dependency Lookup (DL) 의존관계 조회(탐색) 이라한다.

 

그러나 이는 스프링 컨테이너에 종속적인 코드가 된다.

 

 

 

ObjectProvider

Provider를 사용하여 새로운 프로토타입 빈이 생성되게 할 수 있다.

 

스프링에서는 지정한 빈을 컨테이너에서 대신 찾아주는 DL 서비스를 제공하는 ObjectProvider가 있다.

public class PrototypeTest {


    @Test
    public void prototypeBeanFind() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SingletonBean.class,PrototypeBean.class);
        System.out.println("find SingletonBean1");
        SingletonBean singletonBean1 = ac.getBean(SingletonBean.class);
        System.out.println("find singletonBean2");
        SingletonBean singletonBean2 = ac.getBean(SingletonBean.class);
        singletonBean1.logic();
        singletonBean2.logic();
    }


    @Scope("prototype")
    static class PrototypeBean {

    }


    static class SingletonBean {
        @Autowired
        private ObjectProvider<PrototypeBean> prototypeBeanProvider;

        public void logic() {
            PrototypeBean prototypeBean = prototypeBeanProvider.getObject();
            System.out.println("prototypeBean = " + prototypeBean);
        }
    }

}

ObjectProvider의 getObject()를 호출하면 내부에서는 스프링 컨테이너를 통해 해당 빈을 찾아서 반환한다.

 

즉, DL을 자동으로 해주는 것이다.

 

조회할 때마다 다른 인스턴스가 찾아지는 것을 알 수 있다.

 

 

자바 표준 Provider

 

스프링에서 제공하는 ObjectProvider 말고도 자바 표준의 Provider도 있다.

 

대신 해당 라이브러리를 사용하려면 javax.inject:javax.inject:1의 라이브러리를 gradle에 추가하고 사용하여야 한다.

 

public class PrototypeTest {


    @Test
    public void prototypeBeanFind() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SingletonBean.class,PrototypeBean.class);
        System.out.println("find SingletonBean1");
        SingletonBean singletonBean1 = ac.getBean(SingletonBean.class);
        System.out.println("find singletonBean2");
        SingletonBean singletonBean2 = ac.getBean(SingletonBean.class);
        singletonBean1.logic();
        singletonBean2.logic();
    }


    @Scope("prototype")
    static class PrototypeBean {

    }


    static class SingletonBean {
        @Autowired
        private Provider<PrototypeBean> prototypeBeanProvider;

        public void logic() {
            PrototypeBean prototypeBean = prototypeBeanProvider.get();
            System.out.println("prototypeBean = " + prototypeBean);
        }
    }

}

 

마찬가지로 조회할 때마다 다른 프로토타입 빈이 나오는 것을 볼 수 있다.

 

*스프링 Provider vs 자바 표준 Provider

스프링 Provider는 라이브러리를 추가해줄 필요도 없고, DL을 위한 편의 기능을 제공해주기 때문에 편리하지만, 스프링에 종속적이다. 만일 코드를 스프링이 아닌 다른 컨테니어에서도 사용할 수 있어야 한다면 자바 표준, 아니라면 스프링 Provider를 쓰면 좋다.

 

자바 표준과 겹치는 스프링 기능은 대부분 스프링이 더 다양하고 편리한 기능을 제공해주기 때문에 스프링에 제공하는 기능을 사용하면 된다.

 

728x90