이번 글에서는 빈 스코프에 대해서 알아보려고 한다.
일단 빈 스코프라는 단어가 조금 생소하기도 해서 짚고 넘어가겠다.
빈 스코프는 빈이 존재할 수 있는 범위를 말한다.
저번 글에서는 빈의 생명주기를 살펴본 만큼 이번에는 어느 범위 까지 존재할 수 있는 가를 알아보려는 것이다.
빈 스코프의 종류로는 세가지가 있다.
싱글톤
전에도 설명했겠지만 싱글톤 빈은 객체가 1개로 유지된다.
스프링에서 기본적으로 적용되는 빈이기도 하며, 스프링 컨테이너의 시작부터 종료까지 1개의 객체로 유지되는 것이 특징이다.
프로토타입
프로토타입 빈은 싱글톤과는 반대로 빈이 요청될 때마다 초기화 메서드가 호출 된다.
프로토타입 빈의 스코프는 스프링 컨테이너가 시작 될 때 한 번 초기화가 되며, 그 뒤로는 관리를 받지않기에 우리가 따로 관리해주어야만 한다.
웹
다음으로는 웹 스코프이다.
request: 웹 요청이 들어오고 나갈때가지 유지되는 스코프
session: 웹 세션이 생성되고 종료될 때 까지 유지되는 스코프
application: 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프
위와 같은 스코프들이 존재한다.
@Scope(value = "prototype")
@Scope(value = "singleton")
@Scope(value = "request")
@Scope(value = "session")
@Scope(value = "application")
@Scope(value = "websocket")
*** 이런 식으로 스코프 어노테이션을 이용해서 사용가능
가끔 서로 다른종류의 빈을 한 프로젝트에서 사용하는 경우도 있는데, 이럴 때 문제가 생긴다.
예를 들어, 싱글톤 내에 있는 프로토타입 빈을 사용해야 하는 경우가 있다.
싱글톤은 한 개의 객체만 생성되어 관리가 되고, 프로토타입은 그렇지 않으므로
간단히 생각해보면 하나의 싱글톤 객체에서 여러 프로토타입 객체를 쓸 수 있지 않을까? 라는 생각이 들 수 있다.
하지만 그렇지 않다.
위에서 설명했던 것 처럼 프로토타입 빈은 생성시점에 초기화되고 그 뒤로는 관리되지 않는다.
그렇기에 프로토타입 빈을 호출할 때마다 생성이 되기는 하지만 Singleton 안에 있어서 그 참조값이 다르지 않아버리는 일이 발생하고만다.
그렇다면 이런 일을 해결하기 위해서는 어떻게 해야될까?
바로 DL(Dependency Lookup, 의존관계 탐색)을 이용하면 된다.
바로 코드부터 살펴 보도록 하자.
문제가 된 코드
@Test
void singletonClientUserPrototype() {
AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
ClientBean clientBean1 = ac.getBean(ClientBean.class);
ClientBean clientBean2 = ac.getBean(ClientBean.class);
System.out.println("client1 - singleton : " + clientBean1);
System.out.println("client2 - singleton : " + clientBean2);
System.out.println("client1 - prototype : " + clientBean1.prototypeBean);
System.out.println("client2 - prototype : " + clientBean2.prototypeBean);
}
@Scope("singleton")
static class ClientBean {
private final PrototypeBean prototypeBean; // 생성시점에 주입
@Autowired
public ClientBean(PrototypeBean prototypeBean) {
this.prototypeBean = prototypeBean;
}
}
@Scope("prototype")
static class PrototypeBean { }
결과 값
client1 - singleton : hello.spring.ppusda.SingletonWithPrototypeTest$ClientBean@c05fddc
client2 - singleton : hello.spring.ppusda.SingletonWithPrototypeTest$ClientBean@c05fddc
client1 - prototype : hello.spring.ppusda.SingletonWithPrototypeTest$PrototypeBean@25df00a0
client2 - prototype : hello.spring.ppusda.SingletonWithPrototypeTest$PrototypeBean@25df00a0
먼저 문제가 되는 코드이다.
@Scope를 통해 prototype임을 지정했음에도 불구하고 같은 값을 참조하는 것을 볼 수 있다.
이것을 해결 할 때 쓰이는 것이 바로 Provider이다.
Provider 사용
@Test
void singletonClientUserPrototype() {
AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext(ClientBean.class, PrototypeBean.class);
ClientBean clientBean1 = ac.getBean(ClientBean.class);
ClientBean clientBean2 = ac.getBean(ClientBean.class);
System.out.println("client1 - singleton : " + clientBean1);
System.out.println("client2 - singleton : " + clientBean2);
System.out.println("client1 - prototype : " + clientBean1.dl());
System.out.println("client2 - prototype : " + clientBean2.dl());
}
@Scope("singleton")
static class ClientBean {
@Autowired
private Provider<PrototypeBean> prototypeBeanProvider;
public PrototypeBean dl() {
PrototypeBean prototypeBean = prototypeBeanProvider.get(); // 찾아주는 기능을 제공
return prototypeBean;
}
}
@Scope("prototype")
static class PrototypeBean { }
결과 값
client1 - singleton : hello.spring.ppusda.SingletonWithPrototypeTest$ClientBean@480d3575
client2 - singleton : hello.spring.ppusda.SingletonWithPrototypeTest$ClientBean@480d3575
client1 - prototype : hello.spring.ppusda.SingletonWithPrototypeTest$PrototypeBean@50378a4
client2 - prototype : hello.spring.ppusda.SingletonWithPrototypeTest$PrototypeBean@79207381
이번엔 Provider를 사용한 방법이다.
prototype의 참조값이 변한 것을 볼 수 있다.
위에 사용한 Provider는 Java 표준의 기능이다.
이 말고도 Spring에서 지원하는 ObjectFactory, ObjectProvider 가 있는데, 기능은 크게 다르지 않으므로 상황에 맞게 사용하면 될 것 같다.
이처럼 DL은 말그대로 의존관계를 직접 찾아가는 기능을 한다.
다른 스코프를 동시에 사용할 일이 그렇게 많지는 않다고 하지만 분명히 쓰이는 경우가 있다고 하니 알아두는 게 좋을 것 같다.
*** ProxyMode
이 Provider 말고도 특별하게 이를 제어할 수 있는 속성이 있는데, 바로 Proxymode이다.
프록시라고 하면 어디선가 조금 들어봤을 수 도 있는데, 아는 내용이 맞을 것이다.
가짜 프록시객체는 요청이 올 때까지 기다렸다가 요청이 들어오면 그제서야 진짜 빈에게 위임하는 기능을 가지고있다.
웹 스코프 쪽에서도 쓰이니 간단하게 사용하는 방법을 보도록하자.
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyLogger {
}
적용 대상에 따라 Target_Class나 Interfaces으로 바꿔서 사용할 수 있다.
이런 식으로 해두면 가짜 프록시 클래스를 만들어 놓은 채 request가 들어오기 전 부터 주입을 해줄 수 있다.
이런 특별한 스코프는 싱글톤과는 다르기 때문에 조심해서 사용해야하고, 최소한의 상황에서 필요한 부분에만 사용해야만 한다.
***
이렇게 빈 스코프에대해서 알아보았다.
이 글로 강의를 들으면서 배운 내용들에 복습을 해보면서 정리해보는 내용이 끝이나기는 했다.
하지만 앞으로도 Spring 관련 지식들에 대해서 더 습득하게 되는 내용들은 이 곳에 계속 업로드할 것 같다.
어찌보면 복잡한 내용이었지만 재밌어서 배우는 맛이 있었던 것 같다.
'Spring' 카테고리의 다른 글
[Spring MVC] HTML, HTTP API, CSR, SSR (0) | 2022.05.11 |
---|---|
[Spring MVC] 웹 애플리케이션 (0) | 2022.04.25 |
[Spring 핵심 원리] 빈 생명주기 콜백 (0) | 2022.03.20 |
[Spring 핵심 원리] 의존관계 주입에 대하여 (생성자 주입과 Lombok) (0) | 2022.03.20 |
[Spring 핵심 원리] 싱글톤 패턴 (0) | 2022.03.20 |