2022. 8. 31. 17:38ㆍJAVA/Spring
1. 스코프의 종류
- 싱글톤 : 어떤 클래스의 인스턴스 개수가 단 한개만 생성하는 디자인 패턴
- 프로토타입 : 어떤 클래스의 인스턴스를 생성할때마다 새로 생성하는 패턴
- 웹 관련 스코프
- Request : 웹 요청이 들어오고 나갈때까지 유지되는 스코프
- Session : 웹 세션이 생성되고 종료될 때까지 유지되는 스코프
- application : 웹의 서블릿 컨텍스트와 같은 범위로 유지되는 스코프
2. 스프링에서 빈의 스코프는 기본적으로 싱글톤
예를 들어 Single이라는 클래스에 @Component 어노테이션을 적용하면 Single 클래스는 빈으로 등록됩니다. 이때 빈의 스코프를 따로 지정하지 않으면 싱글톤 타입으로 설정됩니다.
@Component
public class Single {
}
@SpringBootTest
public class SingletonTest {
@Autowired
ApplicationContext ctx;
@Test
public void test() throws Exception{
//given
Single single1 = ctx.getBean(Single.class);
Single single2 = ctx.getBean(Single.class);
//when
boolean actual1 = single1.equals(single2);
//then
System.out.println(single1);
System.out.println(single2);
assertThat(actual1).isTrue();
}
}
kr.yh.spring_bean_score.singletom.Single@46bb0bdf
kr.yh.spring_bean_score.singletom.Single@46bb0bdf
위 실행결과를 보면 single1 인스턴스와 single2 인스턴스의 메모리 주소가 동일한 것을 볼 수 있습니다.
3. 빈의 스코프 타입을 프로토타입으로 설정
빈의 스코프 타입을 프로토타입으로 설정하기 위해서는 @Scope 어노테이션의 value를 "prototype"으로 설정하면 됩니다.
@Component
@Scope(value = "prototype")
@Getter
public class Proto2 {
@Autowired
private Single2 single;
}
@Test
public void test() throws Exception{
//given
Proto2 proto1 = ctx.getBean(Proto2.class);
Proto2 proto2 = ctx.getBean(Proto2.class);
//when
boolean actual = proto1.equals(proto2);
//then
System.out.println(proto1);
System.out.println(proto2);
assertThat(actual).isFalse();
}
kr.yh.spring_bean_score.prototype.Proto2@115c946b
kr.yh.spring_bean_score.prototype.Proto2@79ca7bea
위 실행결과를 보면 빈으로 참조한 proto1 인스턴스와 proto2 인스턴스의 메모리 주소가 다른 것을 확인할 수 있습니다. 이는 빈을 참조할때마다 인스턴스를 새로 생성하여 반환한 것임을 알 수 있습니다.
4. 프로토타입 빈이 싱글톤 빈을 참조하는 경우
다음과 같이 Proto 프로토타입 빈이 싱글톤 빈을 참조하는 경우에 싱글톤은 변하지 않기 때문에 참조하는데 문제가 없습니다.
@Component
@Scope(value = "prototype")
@Getter
public class Proto2 {
@Autowired
private Single2 single;
}
@Component
@Getter
public class Single2 {
@Autowired
private Proto3 proto;
}
// 프로토타입 빈이 싱글톤을 참조하는 테스트
@Test
public void test2() throws Exception{
//given
Proto2 proto1 = ctx.getBean(Proto2.class);
Single2 single1 = ctx.getBean(Single2.class);
//when
boolean actual = proto1.getSingle().equals(single1);
//then
System.out.println(proto1.getSingle());
System.out.println(single1);
assertThat(actual).isTrue();
}
kr.yh.spring_bean_score.prototype.Single2@115c946b
kr.yh.spring_bean_score.prototype.Single2@115c946b
위 실행결과와 같이 proto1.getSingle() 호출로 인한 Single2 인스턴스와 빈으로 참조한 single1 인스턴스가 동일한 것을 볼 수 있었습니다. 즉, 프로토타입 빈이 싱글톤 빈을 참조하는 경우에는 별로 문제가 되지 않습니다.
4. 싱글톤 빈이 프로토타입 빈을 참조하는 경우
싱글톤 빈이 초기활 될때 의존 객체에 프로토타입인 의존 객체를 주입하고 다시 인스턴스를 생성하는 일은 없기 때문에 싱글톤 빈이 프로토타입 빈을 참조하는 경우에 프로토타입 빈이 싱글톤이 되는 현상이 발생합니다.
@Component
@Scope(value = "prototype")
public class Proto3 {
}
@Component
@Getter
public class Single2 {
@Autowired
private Proto3 proto;
}
// 싱글톤 타입 빈이 프로토타입 빈을 참조하는 테스트
// 싱글톤 타입 빈의 프로토타입 의존 객체 빈은 업데이트가 되지 않음
@Test
public void test3() throws Exception{
//given
Single2 single1 = ctx.getBean(Single2.class);
Single2 single2 = ctx.getBean(Single2.class);
Proto3 proto1 = single1.getProto();
Proto3 proto2 = single2.getProto();
//when
boolean actual = proto1.equals(proto2);
//then
System.out.println(proto1);
System.out.println(proto2);
assertThat(actual).isTrue();
}
kr.yh.spring_bean_score.prototype.Proto3@61da0413
kr.yh.spring_bean_score.prototype.Proto3@61da0413
위 실행결과와 같이 Proto3 클래스의 빈 스코프를 프로토타입으로 설정했음에도 불구하고 싱글톤 빈이 프로토타입 빈을 참조할때 동일한 객체를 참조하는 것을 볼 수 있습니다. 이는 프로토타입 빈으로 설정한 의도를 무시하는 결과입니다.
프로토타입 빈이 싱글톤 빈처럼 되는 이유는 Proto3 프로토타입 빈은 Single2가 호출될때마다 생성되는 것이 아닌 Single2 싱글톤 빈이 생성될때 의존관계 주입 시점에 딱 한번만 생성되기 때문입니다. 즉, 주입 시점에 스프링 컨테이너에 요청해서 프로토타입 빈이 생성된 것이지, 사용할 때마다 새로 생성되는 것이 아닙니다.
5. 프로토타입 빈의 프록시 모드 설정
프록시 모드란 무엇인가?
- 프록시 패턴은 어떤 객체에 대한 접근을 제어하는 용도로 대리인이나 대변인에 해당하는 객체를 제공하는 패턴입니다.
싱글톤 빈이 프로토타입 빈을 참조하는 경우에 싱글톤으로 참조되는 것을 해결하기 위해서 프로토타입 빈의 스코프를 단순히 프로토타입으로 설정하는 것 뿐만 아니라 프록시 모드로 설정합니다.
@Component
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ProtoProxy {
}
@Component
@Getter
public class Single3 {
@Autowired
private ProtoProxy proto;
}
@Test
public void test4() throws Exception{
//given
Single3 single1 = ctx.getBean(Single3.class);
Single3 single2 = ctx.getBean(Single3.class);
ProtoProxy protoProxy1 = single1.getProto();
ProtoProxy protoProxy2 = single2.getProto();
//when
boolean actual = protoProxy1.equals(protoProxy2);
//then
// protoProxy1은 toString을 호출했기 때문에 서로 다른 메모리 주소가 나옴
System.out.println(protoProxy1);
System.out.println(protoProxy1);
assertThat(actual).isTrue();
}
kr.yh.spring_bean_score.prototype_proxy.ProtoProxy@115c946b
kr.yh.spring_bean_score.prototype_proxy.ProtoProxy@79ca7bea
위의 실행결과를 보면 프록시 모드를 설정했기 때문에 protoProxy1과 protoProxy2의 메모리 주소가 다르게 출력된 것을 볼 수 있습니다.
그런데 위 테스트에서 주목할점은 "protoProxy1.equals(protoProxy2)"의 결과가 true라는 점입니다.
true가 나온 이유는 Single3 클래스의 의존 객체인 ProtoProxy 빈 객체의 스코프는 프로토타입 임에도 불구하고 주입할때는 싱글톤으로 주입되기 때문입니다. 하지만 출력할때는 메모리 주소가 다르게 나온 이유는 프록시 모드를 설정했기 때문에 ProtoProxy 인스턴스가 toString, get과 같은 연산을 호출하는 순간 프록시가 적용되어 새로운 객체를 만든 다음에 그 객체의 연산(toString, get)을 호출하기 때문에 매번 다른 해시코드가 나오는 것입니다. equals의 결과가 true인 이유는 프록시 모드가 수행되지 않고 Object.equals가 호출됬기 때문입니다. 따라서 toString과 같은 출력할때는 서로 다른 메모리 주소가 나오지만 equals과 같은 비교를 할때는 같은 객체로 나온 것입니다.
References
source code : https://github.com/yonghwankim-dev/spring_study/tree/master/spring_bean_score/src/main/java/kr/yh/spring_bean_score
[인프런] 스프링 프레임워크 핵심기술
프록시 패턴(proxy pattern) 이란?
'JAVA > Spring' 카테고리의 다른 글
[Spring][IoC] MessageSource (0) | 2022.09.13 |
---|---|
[Spring][IoC] Environment, 프로파일 및 프로퍼티 (0) | 2022.09.01 |
[Spring][IoC] @Autowired 어노테이션 (0) | 2022.08.29 |
[springboot][Gradle] 자동 설정 만들기 #2 @ConfigurationProperties (0) | 2022.08.17 |
컴포넌트 스캔(ComponentScan)과 빈(Bean) 설정 (0) | 2022.08.15 |