@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 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를 만들 때 인스턴스를 생성하게 되어 있다.