Free Lines Arrow
본문 바로가기
Spring/spring 기초 스터디

[Spring] Bean Scope Prototype: part2

by skahn1215 2021. 6. 12.
728x90
반응형

싱글톤과 프로토타입을 같이 사용했을때 문제점

 

Bean Scope Prototype: Part1 은 아래 페이지에서 확인하자.

https://vprog1215.tistory.com/71

 

[Spring] Bean Scope Prototype: part1

Bean Scope Prototype: part1 Bean Scope의 종류중 하나인 ProtoType 을 알아보자 Bean Scope의 정의는 아래 페이지를 참고하자. https://vprog1215.tistory.com/70?category=989392 [Spring] Bean Scope Bean Sco..

vprog1215.tistory.com

 

 

싱글톤과 프로토 타입을 같이 사용했을때 문제점을 알아보자.

싱글톤빈 안에 프로토타입 빈이 있음.

프로토타입 빈은 count를 증가시키는 로직이 있음. 

 

 

싱글톤 빈 이 프토로 타입 빈을 포함하는경우

 

ClientBean은 싱글톤이다 스프링컨테이너 생성시점에 함께 생성된다.

  • 1. ClientBean은 의존관계 자동주입을 사용한다.
  •  - 주입 시점에 스프링 컨테이너에 프로토타입빈을 요청한다. (중요 포인트)
  • 2. 스프링컨테이너는 프로토타입 빈을 생성해서 clientBean에 반환한다.
  •  - 이제 ClientBean은 프로토타입 빈을 내부 필드에 보관한다(참조값)

 

 

 

싱글톤 빈안에서 프로토타입의 logic 호출

싱글톤 안에서 프로토 타입의 로직을 호출한다.

1. 첫번째 호출 하면 프로토타입의 값을 1 증가시킨다.

2. 두번째 호출 하면 프로토타입의 값을 1 증가시킨다.

 

여기서 문제점이 발생한다.

프로토타입은 원래 한번 쓰고 컨테이너에서 관리를 하지 않는다 

그렇다면 몇번을 호출 해도 값은 1이어야 한다.

하지만 값이 계속해서 증가하게 된다.

1 -> 2 이렇게 말이다. 그림으로 정확하게 살펴보자.

 

 

클라이언트 A

  • 클라이언트 A 가 ClientBean의 logic 을 호출한다.
  • logic() 에서 ProtoType의 addCount()를 호출해 프로토타입 빈의 count를 1 증가시킨다.

 

클라이언트 B

  • 클라이언트 B 는 스프링컨테이너에 요청해 ClientBean을 받는다.
  • 여기서 중요한 점은 clientBean이 내부에 가지고 있는 프로토 타입 빈은 과거 즉 A가 호출한 시점에 주입이 끝난 빈이다. 주입시점에 프로토타입 빈이 새로생성이 되고 그 이후에는 이미 생성된걸 사용한다.
  • logic() 에서 ProtoType의 addCount()를 호출해 프로토타입 빈의 count를 1 증가시킨다.

 

문제가 발생했다.

원래는 프로토타입빈을 사용하여 호출될때마다 새로운 빈을 사용하려는 목적이었는데

앞서 주입과정에서 이미 주입된 프로토타입빈을 계속사용하기 때문이다.

* 프로토타입은 계속 생성되지만 이미주입받은걸 계속사용한다.

 

 

코드구현

package hello.core.scope;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import static org.assertj.core.api.Assertions.*;

public class SingletonWithPrototype1 {

    @Test
    void prototypeFind() {
        // bean 등록 후 가져오기
        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);
    }

    @Test
    void singletonClientUsePrototype() {
    
        // 빈등록
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);

        // clien bean 가져옴
        ClientBean clientBean1 = ac.getBean(ClientBean.class);
        int count1 = clientBean1.logic();
        assertThat(count1).isEqualTo(1);


        // 1이 되어야 하는데 이미 주입받은 Prototyped을 써서 2가 되었다.
        ClientBean clientBean2 = ac.getBean(ClientBean.class);
        int count2 = clientBean2.logic();
        assertThat(count2).isEqualTo(2);
    }

    // ClientBean 은 싱글톤
    @Scope("singleton")
    static class ClientBean {
        // 생성시점에 주입 그래서 계속 같은걸 쓴다.
        // 처음 주입때만 생성한다.
        private final PrototypeBean prototypeBean;

        // 이때 PrototypeBean을 요청하고 찾는다.
        // 이미 생성시점에 만들어 진 애를 사용한다.
        @Autowired
        public  ClientBean(PrototypeBean prototypeBean) {
            this.prototypeBean = prototypeBean;
        }

        // prototype을 써서 값 증가후 값 리턴
        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 inti() {
            System.out.println("PrototypeBean.init" + this);
        }

        @PreDestroy
        public void destroy() {
            System.out.println("PrototypeBean.destroy" + this);
        }

    }

}

 

 

 

해결방법

아래처럼 호줄 해줄때 Bean을 가져온다.

하지만 코드도 지저분 하고 스프링에 너무 의존적인 코드가 된다.

package hello.core.scope;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Scope;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import static org.assertj.core.api.Assertions.assertThat;

public class SingletonWithPrototype1 {

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

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

    // ClientBean 은 싱글톤
    @Scope("singleton")
    static class ClientBean {
        // 생성시점에 주입 그래서 계속 같은걸 쓴다.
        // 처음 주입때만 생성한다.
        // private final PrototypeBean prototypeBean;

        @Autowired
        ApplicationContext ac;

        // 이때 PrototypeBean을 요청하고 찾는다.
        // 이미 생성시점에 만들어 진 애를 사용한다.
       //  @Autowired
       // public  ClientBean(PrototypeBean prototypeBean) {
       //     this.prototypeBean = prototypeBean;
        //}

        public int logic() {
            // 여기가 핵심이다. 그냥 부를때 마다 빈을 가져온다.
            // 그렇게 되면 프로토타입의 동작을 제대로 한다.
            PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
            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");
        }

    }
}

 

 

 

핵심코드

        public int logic() {
            // 여기가 핵심이다. 그냥 부를때 마다 빈을 가져온다.
            // 그렇게 되면 프로토타입의 동작을 제대로 한다.
            PrototypeBean prototypeBean = ac.getBean(PrototypeBean.class);
            prototypeBean.addCount();
            return prototypeBean.getCount();
        }

핵심코드만 나눠서 보자

  • DI 가 아니라 Dependency 를 찾아서 가져온다. 
  • 이러한 방법을 DL(Dependency lookup) 이라고 한다.
  • 하지만 코드가 스프링에 종속적이고 테스트도 힘들다.
  • 그렇다면 DL을 해주는 간편한 방법이 없을까? 스프링에 있다 Part 3에서 알아보자

 

 

 

 

 

참고: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/lecture/55490?tab=curriculum 

 

스프링 핵심 원리 - 기본편 - 인프런 | 학습 페이지

지식을 나누면 반드시 나에게 돌아옵니다. 인프런을 통해 나의 지식에 가치를 부여하세요....

www.inflearn.com

 

728x90
반응형

'Spring > spring 기초 스터디' 카테고리의 다른 글

[Spring] Bean Scope Web: part4  (0) 2021.06.15
[Spring] Bean Scope Prototype: part3  (0) 2021.06.14
[Spring] Bean Scope Prototype: part1  (0) 2021.06.01
[Spring] Bean Scope  (0) 2021.06.01
[Spring] Bean Callback  (0) 2021.05.29

댓글