[스프링 IoC 컨테이너와 빈]_05 빈의 스코프
빈의 스코프
스코프
● 싱글톤
● 프로토타입
○ Request
○ Session
○ WebSocket
○ …
프로토타입 빈이 싱글톤 빈을 참조하면?
● 아무 문제 없음.
싱글톤 빈이 프로토타입 빈을 참조하면?
● 프로토타입 빈이 업데이트가 안되네?
● 업데이트 하려면
○ scoped-proxy
○ Object-Provider
○ Provider (표준)
프록시
싱글톤 객체 사용시 주의할 점
● 프로퍼티가 공유.
● ApplicationContext 초기 구동시 인스턴스 생성.
스코프
-
기본적으로 빈은
싱글톤
스코프를 가진다. -
다른 스코프를 가질 수 있지만 대부분의 경우에는 싱글톤 스코프만 쓰게될 것이다.
-
해당 인스턴스를 어떠한 스코프에 따라 새로 만들어야 되는 경우에는 스포크를 변경해줘야 한다.
싱글톤
애플리케이션 전반에 걸쳐서 해당 빈의 인스턴스가 오직 한개 뿐이다.
※ [실습]
Proto와 Single 클래스를 만들어주고 @Component로 빈등록
Single이 Proto를 가지도록 한다.
@Component
public class Single {
@Autowired
Proto proto;
public Proto getProto() {
return proto;
}
}
@Component
public class Proto {}
ApplicationRunner를 구현한 클래스를 생성해서 직접 주입받은 Proto와 Single이 주입받은 Proto 정보를 출력
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
Single single;
@Autowired
Proto proto;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println(proto); //AppRunner가 주입받은 proto
System.out.println(single.getProto()); //Single 객체가 주입받은 proto
}
}
⇒ 두 인스턴스가 동일하다는 것을 알 수 있다.
프로토타입
매번 새로운 인스턴스를 만들어서 사용하는 스코프
- 빈을 주입받을 때 마다 새로운 인스턴스가 받아진다.
@Component @Scope("prototype")
public class Proto {}
프로토타입 빈과 싱글톤 타입 빈을 3번씩 가져와서 출력해보자
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
ApplicationContext ctx;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("proto");
System.out.println(ctx.getBean(Proto.class));
System.out.println(ctx.getBean(Proto.class));
System.out.println(ctx.getBean(Proto.class));
System.out.println("single");
System.out.println(ctx.getBean(Single.class));
System.out.println(ctx.getBean(Single.class));
System.out.println(ctx.getBean(Single.class));
}
}
⇒ 프로토타입은 호출 시마다 새로운 인스턴스를 만들고, 싱글톤은 동일한 인스턴스임을 확인할 수 있다.
※ 스코프를 혼용해서 사용했을 경우 문제
이렇게 간단하게 빈의 인스턴스의 스코프를 관리할 수 있는 것이 스프링의 장점이다.
하지만 혼용해서 쓰이면 복잡해진다.
※ 프로토타입 빈이 싱글톤 빈을 참조하면?
프로토타입의 빈에 싱글톤을 사용하는 것은 아무 문제가 없다.
Proto 타입의 빈은 매번 새로 생성되겠지만, Proto 타입이 참조하는 Single 톤은 언제나 동일하다.
의도한 대로 쓰이므로 문제가 없다.
@Component @Scope("prototype")
public class Proto {
@Autowired
Single single;
}
※ 싱글톤 빈이 프로토타입 빈을 참조하면?
싱글톤 스코프에서 프로토타입 스코프의 빈을 사용한다면?
싱글톤 빈은 인스턴스가 한번만 만들어진다. 한번 만들어질 때, 이 프로토타입 스코프의 프로퍼티도 이미 셋팅이 된다.
따라서 싱글톤 빈을 계속해서 쓸 때, 프로토타입 스코프의 빈이 새롭게 만들어지지 않고, 처음 셋팅된 인스턴스만 가지고 사용이 된다. 이것이 문제이다.
@Component
public class Single {
@Autowired
Proto proto;
public Proto getProto() {
return proto;
}
}
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
ApplicationContext ctx;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("proto by single");
System.out.println(ctx.getBean(Single.class).getProto());
System.out.println(ctx.getBean(Single.class).getProto());
System.out.println(ctx.getBean(Single.class).getProto());
}
}
⇒ 문제점 : 싱글톤 빈에서 참조하고 있는 프로토타입의 빈의 인스턴스가 변경되지 않는다.
해결방법 1 - proxyMode 설정
쓰기는 쉽지만 이해하기에는 복잡한 방법이다. 기본은 DEFAULT
이다.
- DEFAULT : 프록시를 사용하지 않는다.
- TARGET_CLASS : (CGlib 기반의) 클래스의 프록시를 생성. CGlib를 사용한 다이나믹 프록시가 적용된다.
- INTERFACES : (JDK 기반의) 인스턴스의 프록시를 생성
@Component @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class Proto {
@Autowired
Single single;
}
⇒ 모든 인스턴스가 매번 달라진다.
다른 빈들이 Proto 빈을 사용할 때, (TARGET_CLASS : )클래스 기반의 프록시로 Proto 빈을 감싸라
왜 프록시로 감싸야 하나?
싱글톤 빈이 프로토타입 스코프의 빈을 직접참조하면 안되기 때문이다.
프록시를 한번 거쳐서 참조하도록 해야하기 때문이다. 왜?
직접 참조하면 프로토타입을 매번 바꿔줄 타이밍이 없다. 따라서 매번 새로운 인스턴스로 바꿔줄 수 있는 프록시로 감싸도록 해주는 것이다.
Proto를 상속받아서 Proxy를 만들어주는 CGlib라는 써드파티의 라이브러리가 있다. 클래스도 프록시로 만들 수 있게 해준다.
원래 자바 JDK안에 있는 다이나믹 프록시는 인터페이스의 프록시만 만들어준다.
동작방식
실제 인스턴스를 감싸는 프록시 인스턴스가 만들어지고, 프록시 인스턴스가 빈으로 등록된다. 그리고 실질적으로 프록시 빈을 주입해주는 것이다.
프록시 빈도 Proto를 상속받아서 만든 것이기 때문에 Type 은 같기 때문에 주입이 가능한 것이다.
해결방법2 - 직접 코드로 설정
- 코드에 스프링 코드가 직접적으로 들어가는 방식이라 선호하진 않음
@Component
public class Single {
@Autowired
private ObjectProvider<Proto> proto;
public Proto getProto() {
return proto.getIfAvailable();
}
}
⇒ 두 해결 방법 중 proxyMode를 이용해서 Proto 타입은 포조 스럽게 유지하는 것이 좋아보인다.
롱런 하는 스코프를 가진 빈에서 짧은 생명 주기를 가진 빈들을 주입 받을 때는 어떤 문제가 있고, 어떻게 해결할 수 있는지 앞의 내용을 참고하도록 하자
싱글톤 객체 사용시 주의할 점
하나의 인스턴스가 공유되는데 하나의 변수를 여러 곳에서 수정할 수 있다.
멀티 스레드 환경에서 변수 값이 계속해서 변경되므로 Thread Safe
하지 않다.
A 스레드가 바꾸려는 값과 B 스레드가 바꾸려는 값이 동일한 곳을 바라보고 있다.
⇒ 반드시 Thread Safe 하게 코드를 작성해야 한다.
모든 싱글톤 스코프의 빈들은 기본값이 ApplicationContext를 만들때 생성되도록 되어있다. 애플리케이션 구동할 때 시간이 좀 더 걸릴 수 있다.