<aside> ❗

빈 스코프란 ?

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

[웹 관련 스코프]

[컴포넌트 스캔 자동 등록]

@Scope("prototype")
@Component
public class HelloBean() {}

[수동 등록]

@Scope("prototype")
@Bean
public PrototypeBean helloBean() {
	return new HelloBean();
}

</aside>

<aside> ❗

프로토타입 스코프

[싱글톤 빈 요청]

image.png

  1. 스프링 스코프의 빈을 스프링 컨테이너에 요청한다.
  2. 스프링 컨테이너는 본인이 관리하는 스프링 빈을 반환한다.
  3. 이후에 스프링 컨테이너에 같은 요청이 와도 같은 객체 인스턴스의 스프링 빈을 반환한다.
public class SingletonTest {

    @Test
    void singletonBeanFind() {
        // 컴포넌트 클래스를 인자로 전달하면, 컴포넌트 스캔 대상이 되어 등록이 된다.
        // 그래서 @Component를 작성하지 않아도 됨
        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.close");
        }   
    }
}
/*
출력 결과
SingletonBean.init
singletonBean1 = hello.core.scope.SingletonTest$SingletonBean@54e041a4
singletonBean2 = hello.core.scope.SingletonTest$SingletonBean@54e041a4
SingletonBean.close
*/

[프로토타입 빈 요청 1]

image.png

  1. 프로토타입 스코프의 빈을 스프링 컨테이너에 요청
  2. 스프링 컨테이너는 이 시점에 프로토타입 빈을 생성하고, 필요한 의존관계 주입

[프로토타입 빈 요청 2]

image.png

  1. 스프링 컨테이너는 생성한 프로토타입 빈을 클라이언트에 반환
  2. 이후에 스프링 컨테이너에 같은 요청이 오면 항상 새로운 프로토타입 빈을 생성해서 반환
public class PrototypeTest {

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

        Assertions.assertThat(protoTypeBean1).isNotSameAs(protoTypeBean2);
				
				// @PreDestroy가 동작하지 않으므로 클라이언트가 직접 호출해서 처리해야한다.
        protoTypeBean1.destroy();
        protoTypeBean2.destroy();
        
        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@a8ef162
protoTypeBean2 = hello.core.scope.PrototypeTest$ProtoTypeBean@2eea88a1
*/

[프로토타입 빈의 특징 정리]

정리

</aside>

<aside> ❗

프로토타입 스코프 - 싱글톤 빈과 함께 사용시 문제점

프로토타입 빈 직접 요청

[스프링 컨테이너에 프로토타입 빈 직접 요청 1]

image.png

  1. 클라이언트A는 스프링 컨테이너에 프로토타입 빈을 요청
  2. 스프링 컨테이너는 프로토타입 빈을 새로 생성해서 반환(x01)한다. 해당 빈의 count 필드 값은 0
  3. 클라이언트는 조회한 프로토타입 빈에 addCount()를 호출하면서 count 필드를 + 1한다.

결과적으로 프로토타입 빈(0x1)의 count는 1이 된다.

[스프링 컨테이너에 프로토타입 빈 직접 요청 2]

image.png

  1. 클라이언트B는 스프링 컨테이너에 프로토타입 빈을 요청한다.
  2. 스프링 컨테이너는 프로토타입 빈을 새로 생성해서 반환(x02)한다. 해당 빈의 count 필드 값은 0이다.
  3. 클라이언트는 조회한 프로토타입 빈에 addCount()를 호출하면서 count 필드를 +1 한다.

결과적으로 프로토타입 빈(x02)의 count는 1이 된다.

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

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

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

[싱글톤에서 프로토타입 빈 사용1]

image.png

[싱글톤에서 프로토타입 빈 사용2]

image.png

  1. 클라이언트 A는 clientBean.logic()을 호출한다.
  2. logic()은 clientBean은 prototypeBean의 addCount()를 호출해서 프로토타입의 빈의 count를 증가한다. count값이 1이 된다.

[싱글톤에서 프로토타입 빈 사용3]

image.png

  1. 클라이언트 B는 clientBean.logic()을 호출한다.
  2. clientBean은 기존에 있던 prototypeBean의 addCount()를 호출해서 프로토타입 빈의 count를 증가한다. 원래 count 값이 1 이었으므로 2가 된다.

[정리]

public class SingletonWithPrototypeTest1 {

    @Test
    void singletonClientUserProtoType() {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ProtoTypeBean.class, ClientBean.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);
    }

    @Scope("singleton")
    static class ClientBean {
        private final ProtoTypeBean protoTypeBean;

        @Autowired
        public ClientBean(ProtoTypeBean protoTypeBean) {
            // 생성 시점에 프로토타입빈이 등록됨 
            this.protoTypeBean = protoTypeBean;
        }

        public int logic() {
            // 생성 시점에 등록된 프로토타입을 계속 사용
            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 " + this);
        }
    }
}

[참고]

</aside>

<aside> ❗

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

[스프링 컨테이너에 요청]

    @Scope("singleton")
    static class ClientBean {
        private final ProtoTypeBean protoTypeBean;

        @Autowired
        ApplicationContext ac;

        public int logic() {
            ProtoTypeBean protoTypeBean 
					            = ac.getBean(ProtoTypeBean.class);
            protoTypeBean.addCount();
            return protoTypeBean.getCount();
        }
    }

<aside> ❗

</aside>

<aside> ❗

</aside>

<aside> ❗

</aside>

<aside> ❗

</aside>

<aside> ❗

</aside>

<aside> ❗

</aside>