스프링 Bean의 범위 - 싱글톤과 프로토타입
IoC 컨테이너에서 생성되는 Bean은 기본적으로 싱글톤 방식이다. 즉 JAVA 코드에서 getBean을 통해 객체를 여러번 가져오더라도 실제로 생성되는 객체는 하나이다. 이러한 싱글톤 패턴은 대규모 트래픽 처리시, 객체의 재사용성을 높이고 객체를 여러번 생성하면서 발생하는 메모리 낭비를 방지할 수 있다.
싱글톤 패턴의 단점
직접 싱글톤 패턴을 구현하게되면 여러가지 단점을 가지게 되는데 대표적으로 가지는 단점은 다음과 같다.
- private 접근제어자를 통해 생성되기 때문에 상속이 불가능하다.
- 단위 테스트의 어려움
- 서버환경에서 동시에 요청이 들어오게되면 하나만 생성됨을 보장하지 못한다.
- 전역 상태를 가지기 때문에 객체지향적 관점에 바람직하지 못하다
싱글톤 레지스트리
스프링 컨테이너에서 생성하는 Bean 또한 싱글톤으로 관리되는데, 스프링은 싱글톤 패턴의 단점을 보완한 싱글톤 레지스트리 기능을 통해 Bean을 관리한다. 싱글톤 레지스트리를 통해 보완한 점은 다음과 같다.
- private을 사용하지 않기 때문에 (public 지정가능) 객체지향적 관점에서 바람직하다.
- static으로 지정할 필요가 없기 때문에 전역상태를 가지지 않아도 된다.
- 객체를 테스트하기 용이하다.
싱글톤 확인
직접 코드를 통해 스프링이 Bean을 싱글톤으로 관리하는지 확인해본다.
applicationContext.xml
<bean id="injectionBean" class="scope.ex.InjectionBean" />
<bean id="dependencyBean" class="scope.ex.DependencyBean">
<constructor-arg ref="injectionBean" />
<property name="injectionBean" ref="injectionBean" />
</bean>
dependencyBean에 injectionBean과 setter를 주입해주었다.
DependencyBean.java
public class DependencyBean {
private InjectionBean injectionBean;
public DependencyBean(InjectionBean injectionBean) {
System.out.println("DependencyBean : constructor"); //생성자 호출 시 출력된다.
this.injectionBean = injectionBean;
}
public void setInjectionBean(InjectionBean injectionBean) {
System.out.println("DependencyBean : setter");
this.injectionBean = injectionBean;
}
}
** InjectionBean.java는 코드가 비어있으므로 생략
생성자와 setter 호출을 확인하기 위해 출력문을 추가했다.
MainClass.java
import org.springframework.context.support.GenericXmlApplicationContext;
public class MainClass {
public static void main(String[] args) {
GenericXmlApplicationContext ctx
= new GenericXmlApplicationContext("classpath:applicationContext.xml");
InjectionBean injectionBean = ctx.getBean("injectionBean", InjectionBean.class);
DependencyBean dependencyBean01 = ctx.getBean("dependencyBean", DependencyBean.class);
DependencyBean dependencyBean02 = ctx.getBean("dependencyBean", DependencyBean.class);
//if(dependencyBean01.equals(dependencyBean02))
if(dependencyBean01 == dependencyBean02)
System.out.println("dependencyBean01 == dependencyBean02");
else
System.out.println("dependencyBean01 != dependencyBean02");
System.out.println("dependencyBean01 hashcode" + dependencyBean01.hashCode());
System.out.println("dependencyBean02 hashcode" + dependencyBean02.hashCode());
ctx.close();
}
}
getBean을 통해 객체를 2개 생성한 뒤 해당 객체가 동일한지 확인하기 주솟값을 비교한 뒤 각각 주소를 출력한다.
MainClass.java 실행결과
DependencyBean : constructor
DependencyBean : setter
dependencyBean01 == dependencyBean02
dependencyBean01 hashcode76432244
dependencyBean02 hashcode76432244
싱글톤이 적용되었기 때문에 getBean을 두 번 가져왔음에도 dependencyBean의 생성자와 setter는 한 번 호출되었다.
또한 주솟값이 동일한 것을 확인할 수 있다.
프로토타입 사용
이러한 싱글톤 방식이 마음에들지 않는다면, 스프링설정파일(xml)에서 원하는 Bean에 프로토타입 방식을 설정해주면된다. 프로토타입 방식을 이용하면, 컨테이너에 Bean이 요청 될 때 마다 새로운 객체를 생성하게된다.
applicationContext.xml
<bean id="dependencyBean" class="scope.ex.DependencyBean" scope="prototype">
<constructor-arg ref="injectionBean" />
<property name="injectionBean" ref="injectionBean" />
</bean>
설정방법은 기존 코드에 있는 bean 태그에 scope속성에 prototype을 명시해주면 끝이다.
MainClass.java 실행결과
DependencyBean : constructor
DependencyBean : setter
DependencyBean : constructor
DependencyBean : setter
dependencyBean01 != dependencyBean02
dependencyBean01 hashcode1264413185
dependencyBean02 hashcode1243806178
dependencyBean01 와 dependencyBean02 객체가 생성될 때 마다 생성자와 setter가 실행된 것을 확인할 수 있다.
또한, 주솟값 비교 결과도 달라진 것을 확인할 수 있는데, 프로토타입 방식에 따라 getBean이 호출할 때 마다 새로운 객체를 생성해주었기 때문이다. 즉, new 연산자를 통해 객체를 두 번 생성해준 것과 동일한 결과를 얻었다고 볼 수 있다.
출처 : [인프런] 자바 스프링 프레임워크(renew ver.) - 신입 프로그래머를 위한 강좌