1. 기본 스코프
기본적으로 적용되는 스코프는 앞서 배운바와 같이 싱글톤 스코프이다. 메모리의 낭비 방지, Stateless 설계를 위해서 싱글톤이 필요하다는 것을 배웠었다. 싱글톤 스코프 외에 프로토 타입 스코프도 있다. 프로토타입 스코프 빈은 요청 시마다 개별적인 인스턴스를 반환하기 때문에 사용자의 요청에 따라 계속해서 새로운 객체를 반환해야할 때 쓴다(많이 사용되지는 않는다). @Scope("singleton"), @Singleton("prototype") 으로 빈 스코프를 설정해줄 수 있다(default는 싱글톤 빈). 싱글톤 스코프와 프로토타입 스코프를 아래에서 비교해보자.
싱글톤 스코프
기본적으로 적용되며, 컨테이너의 시작과 종료까지 유지된다. 요청 횟수에 관계없이 1개의 인스턴스만 반환한다.
빈을 두번 불러와도 @PostConstruct의 init() 메서드는 한 번만 호출되는 것을 확인할 수 있다. 역시나 두 개의 인스턴스는 같고, @PreDestroy도 ac.close()에서 적용된 것을 확인할 수 있다.
프로토타입 스코프
빈의 생성과 의존관계 주입 후 적용되며 더는 관리하지 않는다. 조회시마다 새로운 인스턴스를 만들어서 반환해준다. 컨테이너에서 빈을 생성하지만, 생성 이후에는 컨테이너에서 더 이상 빈을 관리하지 않고, 클라이언트에서 처리하도록 내버려둔다. 따라서 @PreDestory 같은 종료 메서드가 호출되지 않는다.
두 번 빈 조회 시 테스트 결과를 보면 각각의 빈이 독립적임을 알 수 있다(isNotSameAs). @PostConstruct의 init() 메서드도 2번 실행되었다. 인스턴스 생성 후 컨테이너에서 관리하지 않으므로, @PreDestroy는 호출되지 않았다. 주석문과 같이 필요 시에는 직접 종료가 필요하다.
2. Provider : 싱글톤 스코프에 프로토타입 스코프를 주입받을 때
싱글톤 스코프 내부의 프로토타입 스코프 빈
싱글톤 스코프에서 프로토타입 스코프인 빈을 주입받으면, 위에서 설명한 프로토타입 스코프의 성질을 잃게 된다. 다시 말해서 프로토타입 빈의 상태값이 유지된다. 아래 코드를 보자.
프로토타입 스코프의 빈을 먼저 작성한다. count 필드가 있고, 간단한 addCount 및 getCount 함수가 있다. 만약 컨테이너에서 이 빈을 조회하면, 프로토타입 스코프이므로 조회 시마다 인스턴스가 새롭게 생성되어 각 인스턴스의 count 필드값은 0일 것이다.
그러나 이 프로토타입 빈이 싱글톤 스코프에 들어가면, 다른 상황이 전개된다. 싱글톤 빈이 생성되는 시점에 생성자 주입을 통해서 프로토타입이 주입되면, 이 때 생성된 프로토타입 빈은 싱글톤 스코프 내부에서 생성된 채로 남아있고, 컨테이너에 의해서 관리되는 객체가 되어버린다.
따라서 ClientBean에서 add() 메서드를 실행하여 의존관계를 주입받는 prototypeBean의 상태값을 바꿔주면, 상태값이 유지된채로 적용이 되기 때문에 실행 시마다 count 값이 올라가게 되는 것이다. 프로토타입 스코프를 사용하는 이유는 해당 빈을 사용 시마다 새로운 인스턴스가 사용되게 하기 위함인데, 싱글톤 스코프에 주입되는 프로토타입 빈은 이런 원리가 깨지게 되는 문제가 발생했다. 이 문제를 해결하기 위한 방법은, ClientBean 내부에 ApplicationContext를 생성하고, add() 메서드 내부에서 해당 컨테이너에 PrototypeBean.class를 등록해서 add() 메서드가 호출될 때마다 컨테이너에서 PrototypeBean을 생성하도록 하면 된다. 이렇게 하면 필요할 때마다 프로토타입 빈을 새롭게 요청할 수 있게 된다. 그러나 이 방법은 복잡하기 때문에, 다음 섹션의 Provider 개념을 적용한다.
3. Provider : Dependency Lookup
ObjectProvider<T> 사용해보기
Provider는 지정한 빈을 컨테이너 대신 찾아주는 기능을 한다. 지금 필요한 기능은 싱글톤 빈에 의존관계 주입이 되는 프로토타입 빈을 찾아와야 하는 것인데, 이렇게 직접 필요한 의존관계를 찾는 것을 의존 관계 조회(Dependency Lookup, DL) 이라고 한다.
위에서 ApplicationContext 자체를 주입받아와서 PrototypeBean을 조회하는 방법을 사용했지만, 이 방법은 스프링 컨테이너에 의존하는 코드가 되고, 단위 테스트가 어렵게 하는 요소가 되므로 좋은 방법이 아니다. 따라서 DL의 기능만 할 수 있는 어떤 것이 필요한데, 이것이 Provider이다.
ApplicationContext 대신, ObjectProvider<T>를 주입하면 된다. 그리고 해당 Provider에서 .getObject() 메서드를 이용해서 원하는 빈을 꺼내면 된다.
싱글톤에서 프로토타입 빈을 찾는 용도로 사용했지만, Provider는 그런 개념에 국한되는 것이 아니라 컨테이너 대신 의존관계에 있는 빈을 직접 찾아와주는(DL) 기능을 하는 클래스라는 것을 기억해야 한다.
위 사진에서 보면, ObjectProvider<T>는 ObjectFactory<T>에 의존함을 알 수 있다. ObjectFactory에서 기능을 몇 개 추가한 개념이 ObjectProvider라고 보면 된다. 그리고 이 두 클래스 모두 스프링에 의존하는 코드이다.
스프링에 의존하지 않는 자바 표준 기술을 사용하는 방법이 있다. 자바 표준 JSR-330의 Provider<T>를 사용하면 된다.
JSR-330 Provider<T>
우선 자바 표준 라이브러리를 import 받아야한다. build.gradle 파일에서 javax dependency를 추가하고, 새로 고침을 눌러 다시 빌드를 한다.
이제 ObjectProvider를 Provider로 변경해주면 된다. 반드시 javax.inject의 Provider를 사용해야 한다. 그리고 빈을 호출하는 메서드는 getObject -> get 으로 변경해주면 된다.
실무에서 Provider를 직접 사용할 일은 거의 없다. 그러나 Provider의 개념은 다른 곳에서도 쓰이기 때문에(lazy&optional loading, circular dependency, proxy 등) 이런 개념이 있다는 것을 알아둘 필요가 있다.
참조
1. 인프런_스프링 핵심 원리 기본편_김영한 님 강의
'Programming-[Backend] > Spring' 카테고리의 다른 글
[스프링 웹MVC] 1. 웹 애플리케이션 이해 (0) | 2021.07.17 |
---|---|
[스프링 기초] 17. 빈 스코프 : 웹 스코프와 프록시 (0) | 2021.07.16 |
[스프링 기초] 15. 빈 생명주기 콜백 : initMethod, destroyMethod, @PostConstruct, @PreDestroy (0) | 2021.07.11 |
[수정중][스프링 기초] 14. 여러 개의 빈이 있을 때 자동주입하기, @Primary, @Qualifier, NoUniqueBeanDefinitionException (0) | 2021.07.07 |
[스프링 기초] 13. 의존관계 주입 : 4가지 방법 (0) | 2021.07.04 |