on
IoC 컨테이너 5부
스프링 프레임워크 핵심기술을 공부하고 정리하는 포스트입니다.
빈의 스코프
빈의 Scope에는 싱글톤과 프로토타입이 있는데, 스프링 IoC 컨테이너에 등록되는 빈들은 기본적으로 싱글톤으로 등록이 된다. 싱글톤은 해당 빈의 인스턴스를 오직 하나만 생성해 모든 Application에 사용하는 것을 말하며 프로토타입(Prototype)은 매번 다른 객체를 생성하는 것이다.
- Singleton(싱글톤)
- 기본(Default) 스코프. 애플리케이션 전반에 걸쳐 해당 빈의 인스턴스가 오직 한 개인 것
- Prototype(프로토타입)
- 애플리케이션이 요청할 때마다 새로운 인스턴스를 생성하는 것
▶ 싱글톤 스코프
package me.gracenam.demospring51;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Single {
@Autowired
private Proto proto;
public Proto getProto() {
return proto;
}
}
package me.gracenam.demospring51;
import org.springframework.stereotype.Component;
@Component
public class Proto {
}
package me.gracenam.demospring51;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
Single single;
@Autowired
Proto proto;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println(proto);
System.out.println(single.getProto());
}
}
Single 클래스와 Proto 클래스 모두 빈으로 등록하고 Single 클래스에서 Proto 타입의 필드를 getter로 가져온다.
이제 각각을 출력했을 때 어떻게 출력이 되는지 확인하기 위해 AppRunner 클래스에 run 메소드를 만들어서 찍어보겠다. 첫 번째 proto는 AppRunner가 주입받은 proto이고 두 번째는 Single이 참조하고 있는 proto인데 이 두 인스턴스가 같을 것이다.
같은 값이 출력되는 것을 확인할 수 있다. 이렇게 애플리케이션 전체에서 오직 해당 빈의 인스턴스 하나만 사용하는 것이 싱글톤이다. 대부분의 경우에는 싱글톤 스코프만 쓰게 될 것이다.
▶ 프로토타입
만일 인스턴스를 특정 스코프에 따라 새로 만들어야 하는 경우에는 스코프를 변경해 주어야 한다. Request, Session, WebSocket 등등이 있는데 모두 프로토타입과 유사하다.
프로토타입이라는 스코프는 매번 새로운 인스턴스를 만들어서 써야한다.
package me.gracenam.demospring51;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component @Scope("prototype")
public class Proto {
}
package me.gracenam.demospring51;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@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));
}
}
프로토타입 스코프를 준 상태에서 인스턴스가 어떻게 찍히는지 확인해보자. 프로토타입은 매번 다르게 찍힐 것이고 싱글톤은 같을 것이다.
이렇게 프로토타입 따로 싱글톤 따로 쓰일 경우에는 간단하지만 만약에 섞이게 되면 굉장히 복잡해진다.
▶ 프로토타입이 싱글톤을 참조
프로토타입의 빈이 싱글톤 스코프의 빈을 참조해서 쓰는 경우에는 아무 문제가 없다.
package me.gracenam.demospring51;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component @Scope("prototype")
public class Proto {
@Autowired
Single single;
}
싱글톤 스코프 빈은 의도한데로 매번 같은 인스턴스가 들어올 것이고 프로토타입은 매번 바뀌지만 프로토타입이 참조하는 싱글톤 스코프의 빈은 항상 동일할 것이다.
▶ 싱글톤이 프로토타입을 참조
반면에 싱글톤 스코프에서 프로토타입을 참조할 때는 문제가 생긴다. 싱글톤 스코프의 빈은 싱글톤이기 때문에 인스턴스가 한 번만 만들어지는데 이 때 프로토타입의 프로퍼티가 이미 세팅이 되어버린다. 그렇기 때문에 싱글톤 스코프의 빈을 사용할 때 프로토타입의 프로퍼티가 변경되지 않는 문제가 생긴다.
package me.gracenam.demospring51;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Single {
@Autowired
private Proto proto;
public Proto getProto() {
return proto;
}
}
package me.gracenam.demospring51;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@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));
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());
}
}
싱글을 거쳐서 프로토를 찍으면 아래와 같이 나온다.
보시다싶이 싱글을 거친 경우에는 프로토타입임에도 불구하고 모두 같다.
업데이트하는 방법
▶ scoped-proxy
이것을 해결하는 방법은 여러가지가 있는 그 중에 사용하기는 쉽지만 이해하기는 조금 어려운 방법은 proxyMode
를 설정해주는 것이다.
proxyMode는 기본값이 ScopedProxyMode.Default
로 되어있고 이 옵션은 프록시를 사용하지 않는다는 것이다. 지금 사용하는 예제에서는 interface가 아닌 class이기 때문에 ScopedProxyMode.TARGET_CLASS
를 사용하고 이 경우 CG 라이브러리1를 사용한 다이나믹 프록시2가 적용이 된다.
package me.gracenam.demospring51;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
@Component @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class Proto {
}
놀랍게도 매번 다르게 출력이 된다! 과연 어떻게 동작을 했기에 이게 가능한걸까?
proxyMode
를 쓴다는 것은 프록시를 쓴다는 것이고 Proto
클래스를 프록시로 감싸라고 알려주는 것이다. 그 중에서도 ScopedProxyMode.TARGET_CLASS
, 클래스 기반의 프록시로 빈을 감싸서 다른 빈들이 이 빈을 사용할 때 감싼 프록시 빈을 쓰게해라고 설정한 것이다.
왜 프록시로 감싸야할까? 싱글톤 인스턴스들이 프로토타입 스코프의 빈을 직접 참조하면 안되기 때문이다. 프로토타입을 매번 새로운 인스턴스로 바꿔줘야 하는데 직접 참조하게 되면 바꿔줄 여지가 없다. 따라서 매번 바뀌줄 수 있는 프록시로 감싸도록 하는 것이다.
Single에서 주입받은 Proto는 사실 프록시 빈인 셈이다.
▶ Object-Provider
Scoped-Proxy가 복잡하고 성능에도 영향을 주는 것 같다고 느낀다면 Object-Provider를 사용해보자.
package me.gracenam.demospring51;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component @Scope(value = "prototype")
public class Proto {
}
package me.gracenam.demospring51;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Single {
@Autowired
private ObjectProvider<Proto> proto;
public Proto getProto() {
return proto.getIfAvailable();
}
}
이것도 마찬가지로 오브젝트가 바뀌어서 출력될 것이다. 하지만 이 방법의 경우 소스코드를 직접 건드려야서 코드 자체에 스프링 소스가 들어가버리기 떄문이다.
싱글톤 객체 사용 시 주의할 점
싱글톤 객체는 프로퍼티가 공유가 된다. 예를 들어, value라는 값이 들어 있고 그 값을 여러 곳에서 막 고쳐 쓴다고 생각해보자.
package me.gracenam.demospring51;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Single {
@Autowired
private Proto proto;
int value = 0;
public Proto getProto() {
return proto;
}
}
value의 값이 쓰레드 세이프, 즉 안정적이라고 보장 받을 수 없다. 멀티 쓰레드 환경에서는 값이 마구잡이로 바뀔텐데 A라는 쓰레드와 B라는 쓰레드에서 바꾼 값이 서로 다를 경우 문제가 발생할 수 있다. A와 B 모두 같은 곳을 바라보는데 A에서 1로 바꿨는데 B에서 2로 바꾼 경우 A가 읽어서 출력하면 2가 나올 수 있는 것이다.
그리고 이 모든 싱글톤 스코프의 빈들은 ApplicationContext를 만들 때 만들도록 되어있어 초기 구동 시 약간의 시간이 걸린다는 단점이 있다.
Reference
Comments
SPRING 의 다른 글
-
스프링 타입 컨터버 24 Jun 2022
-
API 예외 처리 17 Jun 2022
-
예외 처리와 오류 페이지 12 Jun 2022
-
로그인 처리 - 인터셉터 08 Jun 2022
-
로그인 처리 - 필터 06 Jun 2022
-
로그인 처리 - 쿠키, 세션 31 May 2022
-
Bean Validation 22 May 2022
-
검증 22 May 2022
-
메시지, 국제화 21 May 2022
-
타임리프 - 스프링 통합과 폼 19 May 2022
-
타임리프 - 기본 기능 10 May 2022
-
스프링 MVC 기본 기능 - 웹 페이지 만들기 02 May 2022
-
스프링 MVC 기본 기능 - HTTP 응답 30 Apr 2022
-
스프링 MVC 기본 기능 - HTTP 요청 24 Apr 2022
-
스프링 MVC 기본 기능 - 요청 매핑 19 Apr 2022
-
스프링 MVC 기본 기능 19 Apr 2022
-
스프링 MVC 구조 이해 14 Apr 2022
-
MVC 프레임워크 만들기 - V4, V5 12 Apr 2022
-
MVC 프레임워크 만들기 - V1, V2, V3 09 Apr 2022
-
서블릿, JSP, MVC 패턴 05 Apr 2022
-
서블릿 29 Mar 2022
-
웹 애플리케이션 이해 24 Mar 2022
-
스프링 웹 계층이란? 05 Nov 2021
-
스프링 시큐리티 공식문서 번역 27 Sep 2021
-
스프링 AOP 총정리 : 개념, 프록시 기반 AOP, @AOP 27 Apr 2021
-
SpEL (스프링 Expression Language) 25 Apr 2021
-
데이터 바인딩 추상화 : Converter와 Formatter 21 Apr 2021
-
데이터 바인딩 추상화 : PropertyEditor 12 Apr 2021
-
Validation 추상화 10 Apr 2021
-
Resource 추상화 08 Apr 2021
-
IoC 컨테이너 9부 07 Apr 2021
-
IoC 컨테이너 8부 06 Apr 2021
-
IoC 컨테이너 7부 02 Apr 2021
-
IoC 컨테이너 6부 29 Mar 2021
-
IoC 컨테이너 5부 27 Mar 2021
-
IoC 컨테이너 4부 23 Mar 2021
-
IoC 컨테이너 3부 20 Mar 2021
-
IoC 컨테이너 2부 18 Mar 2021
-
IoC 컨테이너 1부 12 Mar 2021
-
스프링 PSA 07 Jan 2021
-
스프링 @AOP 실습 07 Jan 2021
-
프록시 패턴 06 Jan 2021
-
스프링 AOP 04 Jan 2021
-
의존성 주입(Dependency Injection) 04 Jan 2021
-
스프링 빈(Bean) 02 Jan 2021
-
스프링 IoC 컨테이너 01 Jan 2021
-
스프링 IoC 01 Jan 2021