빈의 스코프
싱글턴
애플리케이션 전반에 걸쳐 빈의 인스턴스가 오직 하나 뿐인 것을 말한다.
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
Single single;
@Autowired
Proto proto;
@Override
public void run(ApplicationArguments args) throws Exception {
// AppRunner가 불러오는 proto
System.out.println(proto);
// Single이 불러오는 proto
System.out.println(single.getProto());
}
}
@Component
public class Single {
@Autowired
Proto proto;
public Proto getProto() {
return proto;
}
}
@Component
public class Proto {
}
me.whiteship.beanscope.Proto@3c49fab6
me.whiteship.beanscope.Proto@3c49fab6
주입 받은 proto
와 single
객체에서 불러온 proto
의 주소가 같은 것을 확인할 수 있다.
프로토타입
매번 새로운 인스턴스를 생성하는 것이다.
Request
Session
WebSocket
@Component
@Scope("prototype")
public class Proto {
}
Proto
에 Scope
을 prototype으로 정의해놓으면
me.whiteship.beanscope.Proto@28f8e165
me.whiteship.beanscope.Proto@545f80bf
이렇게 서로 다른 주소값이 나오게 된다.
@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("\nsingle");
System.out.println(ctx.getBean(Single.class));
System.out.println(ctx.getBean(Single.class));
System.out.println(ctx.getBean(Single.class));
}
}
proto
me.whiteship.beanscope.Proto@632aa1a3
me.whiteship.beanscope.Proto@20765ed5
me.whiteship.beanscope.Proto@3b582111
single
me.whiteship.beanscope.Single@2899a8db
me.whiteship.beanscope.Single@2899a8db
me.whiteship.beanscope.Single@2899a8db
여러 번을 시도해도 역시 같은 결과를 가져온다.
프로토타입 빈이 싱글턴 빈을 참조하는 경우
@Component
@Scope("prototype")
public class Proto {
@Autowired
Single single;
}
프로토타입 빈은 항상 새롭지만 안에서 참조하는 싱글턴 빈은 항상 동일하므로 아무 문제가 없다.
싱글턴 빈이 프로토타입 빈을 참조하는 경우
싱글턴 빈은 한 번만 만들어지므로 이미 프로토타입의 빈이 이미 세팅된 상태다.
따라서 프로토타입 빈이 변경되지 않는다.
@Component
public class AppRunner implements ApplicationRunner {
@Autowired
ApplicationContext ctx;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("\nproto 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());
}
}
proto by single
me.whiteship.beanscope.Proto@1162410a
me.whiteship.beanscope.Proto@1162410a
me.whiteship.beanscope.Proto@1162410a
코드로 테스트 해보니 역시 프로토타입임에도 같은 주소값을 출력한다.
해결법
프록시 모드를 설정하면 된다. 기본은 DEFAULT(프록시 모드 사용 안 함)으로 되어있다.
@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class Proto {
@Autowired
Single single;
}
proto by single
me.whiteship.beanscope.Proto@4a951911
me.whiteship.beanscope.Proto@55b62629
me.whiteship.beanscope.Proto@a53bb6f
매번 새로운 인스턴스가 만들어짐을 확인할 수 있다.
TARGET_CLASS
클래스 기반의 프록시로 빈(
Proto
)를 감싸라고 설정하는 것프록시를 쓰지 않으면 직접 참조하므로 프로토타입을 생성할 때마다 새로 바꿔 줄 여지가 없다.
프록시를 거쳐서 참조하도록 하면 매번 바꿔서 참조할 수 있다.
원래 자바 안에 있는 다이내믹 프록시는 인터페이스의 프록시만 만들 수 있다.
CG LIB(Code Generator Library
라는 서드 파티 라이브러가TARGET_CLASS
설정을 보고 클래스도 만들 수 있도록 한다.만약 인터페이스였다면
INTERFACES
로 설정해서 인터페이스 기반의 프록시를 썼을 것이다.
프록시 빈도 프로토타입의 빈을 상속해서 만들었기 때문에 해당 빈과 타입(여기서는
Proto
타입)은 같다.따라서 의존성 주입이 가능한 것이다.
Reference
싱글턴 객체 사용 시 주의할 점
프로퍼티에 담긴 값이 안정적일 것이라고, thread-safe 할 것이라고 생각해서는 안 된다.
@Component
public class Single {
@Autowired
Proto proto;
// 이 값이 그대로일 거라고 보장하지 못한다.
int value = 0;
public Proto getProto() {
return proto;
}
}
싱글턴 객체는 ApplicationContext를 만들 때 인스턴스를 생성하게 되어 있다.
따라서 애플리케이션 구동에 시간이 걸릴 수 있다.
Last updated
Was this helpful?