당니의 개발자 스토리
프로토타입 스코프 본문
프로토타입 스코프
프로토타입 스코프에 대해서 알아보겠습니다.

싱글톤 같은 경우에는 기본으로 만들면 싱글톤으로 된다고 했죠. 싱글톤 스코프의 빈을 조회하면 스프링 컨테이너는 싱글톤이니까 항상 같은 인스턴스의 스프링 빈을 반환하는게 보장이 된다고 했었죠.
반면에 프로토타입 스코프를 스프링 컨테이너에서 조회하면, 스프링 컨테이너는 조회할 때마다 항상 새로운 인스턴스를 만들어서 반환을 해줍니다.
그림으로 비교를 해보면,

클라이언트 A, B, C가 memberService 라는 초록 색깔 스프링 빈을 요청하는데, 이 빈의 스코프가 싱글톤입니다. 그러면 3번 요청을 해도 항상 같은 거를 반환해주죠.

그래서 스프링 컨테이너에 싱글톤 스코프의 빈을 요청하면, 스프링 컨테이너는 본인이 이미 관리하던 스프링 빈을 반환하는 거예요.
이후에 스프링 컨테이너가 같은 요청이 와도, 같은 객체 인스턴스의 스프링 빈을 반환합니다.
그래서 사실 클라이언트 A, B, C는 똑같은 memberService의 주소가 x01로 되어 있는 스프링 빈을 공유해서 사용합니다.
그럼 두 번째 프로토타입 같은 경우에 어떻게 되냐면, 그림을 나눠그렸는데

클라이언트 A, B, C가 동시든, 순차든 prototypeBean 이라는 이름의 빈을 요청을 해요. 이 빈은 스코프가 프로토타입이에요. 일단 클라이언트 A가 먼저 스프링 컨테이너한테 '이름이 prototypeBean인 빈을 줘!' 라고 요청하면, 스프링 컨테이너가 그때 이 prototypeBean 빈을 객체 생성을 해요.
그래서 생성한 객체에다가 필요한 DI(의존관계 주입)까지 다 하고, 초기화 메서드까지 호출한 다음에 딱 완성이 되면,

이렇게 클라이언트 A한테 던지고 버려버려요. 그게 끝이에요. 더 이상 스프링 컨테이너가 관리하지 않아요.
그리고 나서 또 클라이언트 B가 요청을 하잖아요. 그럼 또 새로운걸 만들어요. 그리고 반환하고 끝.
또 C가 요청을 하잖아요. 그럼 또 새로운걸 만들어요. 그리고 반환하고 스프링 컨테이너가 더이상 관리를 안하는거에요. 한마디로 딱 생성하고 의존관계 주입 하고, 그냥 리턴하고 끝내는 거예요. 스프링 컨테이너가 더이상 프로토타입 빈을 가지고 있지 않아요.

그래서 프로토타입 스코프의 빈을 스프링 컨테이너에 요청을 하면, 스프링 컨테이너는 이 시점에 프로토타입 빈을 생성을 해요. 딱 요청하는 시점에 생성하는 거예요. 그리고 필요한 의존관계를 주입을 하고, 초기화 메서드까지 딱 불러줘요.
그 다음에

스프링 컨테이너는 생성한 프로토타입 빈을 클라이언트에 반환을 하고요.
이후에 스프링 컨테이너에 같은 요청이 오면, 항상 새로운 프로토타입 빈을 생성해서 반환합니다.
정리해보면,


여기서 핵심은 스프링 컨테이너는 프로토타입 빈을 생성하고, 의존관계 주입, 초기화까지만 처리한다는 거예요. 클라이언트에 빈을 반환하고 난 이후부터는 스프링 컨테이너가 생성된 프로토타입 빈을 관리하지 않습니다.
그럼 그걸 누가 관리해요? 예를 들어서 프로토타입 빈을 했는데 종료 메서드를 호출해줘야 돼요.
그걸 누가 관리하냐? 빈을 반환받은 클라이언트가 그때부터 책임을 가지고 종료 메서드를 호출해줘야 됩니다. 원래는 스프링이 빈이 종료되기 직전에 소멸 콜백을 호출해줬어요. 그런데 스프링 컨테이너가 클라이언트한테 프로토타입 빈을 반환해주고 나면, 더이상 빈을 가지고 있지 않죠. 관리하지 않잖아요. 그러니까 스프링이 아니라, 클라이언트가 이제는 책임지고 종료 메서드까지 호출을 해줘야되는 거예요.
그래서 프로토타입 빈을 관리할 책임은 프로토타입 빈을 받은 클라이언트에 있는 거예요.
그래서 @PreDestroy 같은, 스프링 컨테이너가 호출하는 종료 메서드 자체가 아예 호출되지 않아요.
이제 코드를 짜서 확인해볼게요.

test에다가 scope 라는 패키지를 만들겠습니다.

그리고 SingletonTest 라는 클래스를 만들겠습니다.

우선 싱글톤 빈을 하나 만들게요.

스코프를 설정할 건데, 어차피 싱글톤이 디폴트라서 안해도 되지만, 일단은 적어볼게요.

그리고 나서, 싱글톤 스코프 빈으로 올라갈 이 클래스를 AnnotationConfigApplicationContext를 통해서 컴포넌트 스캔으로 읽을 겁니다.

여기 파라미터에 클래스를 넣어주면, SingletonBean.class가 자동으로 컴포넌트 스캔으로 해서, 빈으로 등록이 됩니다.
그 다음에 getBean 해서 조회해볼게요.

출력하고 검증까지 하겠습니다. 검증은 == 비교입빈도 니다.

돌려보면, 똑같은 인스턴스인 걸 확인하고 테스트가 성공합니다. 그런데 지금 destroy()가 안 찍힌 건, 스프링 컨테이너를 close 안 해줘서 그런 거예요.
싱글톤 빈은 스프링 컨테이너가 종료되기 직전에 함게 종료된다고 했어요. 컨테이너가 종료가 안 됐으니까 당연히 소멸 콜백을 못 받아서 destroy()를 호출 못 한거예요.

close 해주고 다시 돌리면,

init과 destroy가 둘 다 호출된 걸 확인할 수 있죠.

그래서 스프링 컨테이너가 종료될 때, close를 해야 종료까지 되는구나. 라는 걸 확인하실 수 있습니다.
이번에는 프로토타입 빈 스코프를 테스트 해보겠습니다. scope 패키지 안에다가

PrototypeTest 라는 프로토타입 빈을 찾는 테스트를 만들겠습니다.

그리고 나서, 프로토타입 빈으로 등록할 클래스를 만들겠습니다.

그리고 나서, 스프링 컨테이너를 띄우면서 이 프로토타입 빈을 등록하고, 테스트를 진행하면 됩니다.

System.out.println("find prototypeBean1")을 왜했냐면, 프로토타입 빈은 조회하기 직전에 생성된다고 했잖아요. 그걸 확인하기 위해서 입니다.

이렇게 하면 됩니다.

참고로 AnnotationConfigApplicationContext의 파리미터로 이 클래스가 전달되면서, 컴포넌트 스캔으로 빈이 등록된다고 했는데,

@Component 적어줘야 되는 거 아닌가요?

AnnotationConfigApplicationContext의 파리미터로 전달되면, 그냥 이 클래스가 컴포넌트 스캔의 대상 자체로 동작하기 때문에, 클래스를 바로 컴포넌트 스캔으로 스프링 빈으로 등록해버립니다. 그래서 @Component가 없어도 있는 것처럼 동작합니다.
돌려보면,

순서대로 find prototypeBean1이 출력되고, 그 다음에 PrototypeBean.init이 호출됐죠. 조회를 할 때, init이 호출된 거예요. 이때 만들어진 거죠.
그리고 find prototypeBean2를 출력하고, 또 PrototypeBean.init이 나오죠. 이 때 새로운 객체가 또 생성되면서, init이 호출된 거예요.
한 번만 만들어졌으면 init이 한 번 호출됐을 텐데, 지금 조회한 만큼 init이 호출된 거죠. 그리고 생성된 객체는 역시 서로 다른 참조값을 가진 서로 다른 객체입니다.
destroy를 출력하기 위해서, close를 해보겠습니다.

그런데 어라?? 스프링 컨테이너가 종료되고 있는데, 프로토타입 빈이 전혀 클로즈가 안 되죠. 스프링 컨테이너가 진짜 프로토타입 이름 그대로 만들고 버린 거예요. 그래서 컨테이너와 더 이상 관련이 없으니까 스프링 컨테이너가 종료되어도 프로토타입 빈은 destroy()를 호출하지 않는 겁니다.
이제 정리를 하겠습니다.


싱글톤 빈은 스프링 컨테이너 생성 시점에 초기화 메서드가 실행 되지만, 프로토타입 스코프의 빈은 스프링 컨테이너에서 빈을 조회할 때 생성되고, 초기화 메서드도 이 때 실행됩니다.
프로토타입 빈을 2번 조회했으므로 완전히 다른 스프링 빈이 생성되고, 초기화도 2번 실행된 것을 확인할 수 있습니다.
그리고 싱글톤 빈은 스프링 컨테이너가 관리하기 때문에 스프링 컨테이너가 종료될 때 빈의 종료 메서드가 실행됐어요.
그런데 프로토타입 빈은 스프링 컨테이너가 관리를 안 해요. 딱 생성하고 클라이언트한테 넘겨버려요. 그렇기 때문에 스프링 컨테이너가 생성과 의존관계 주입 그리고 초기화까지만 관여하고, 더는 관리하지 않습니다. 따라서 프로토타입 빈은 스프링 컨테이너가 종료될 때 @PreDestroy 같은 종료 메서드가 전혀 실행되지 않습니다.
만약에 꼭 destory()가 호출되어야 하면, 지금 클라이언트가 PrototypeTest니까, '아이고 이제 너를 닫아야겠구나' 하고

이렇게 수동으로 적어줘서

수작업을 해주면 됩니다.
그래서 프로토타입 빈의 특징을 정리해보면,

프로토타입 빈은 스프링 컨테이너에 요청할 때 마다 새로 생성됩니다.
그리고 스프링 컨테이너는 프로토타입 빈의 생성과 의존관계 주입 그리고 초기화까지만 관여합니다. 그래서 종료 메서드가 호출되지 않습니다.
따라서, 프로토타입 빈은 프로토타입 빈을 조회한 클라이언트가 관리해야 됩니다. 종료 메서드에 대한 호출도 클라이언트가 직접 해줘야 됩니다.
여기까지만 보면 간단한데, 언제 간단하지 않냐?
보통 대부분 싱글톤 빈을 쓰고, 프로토타입 빈은 진짜 어쩌다가 쓰게 되거든요.
그러면 싱글톤 빈이랑 이 프로토타입 스코프의 빈을 같이 쓰게 돼요. 그러면 중대한 문제가 발생합니다.
다음 시간에는 그 문제점에 대해서 알아보겠습니다.
'스프링 > 스프링 핵심 원리 - 기본편' 카테고리의 다른 글
| 프로토타입 스코프 - 싱글톤 빈과 함께 사용시 Provider로 문제 해결 (0) | 2024.01.28 |
|---|---|
| 프로토타입 스코프 - 싱글톤 빈과 함께 사용시 문제점 (0) | 2024.01.27 |
| 빈 스코프란? (0) | 2024.01.27 |
| 애노테이션 @PostConstruct, @PreDestroy (0) | 2024.01.26 |
| 빈 등록 초기화, 소멸 메서드 (0) | 2024.01.26 |