SW-PRODUCT/개발-Mentor

Spring의 Singleton과 Java static기반 Singleton 패턴의 차이

굴돌 2015. 2. 2. 21:06



Spring의 Singleton과 Java static을 이용한 Singleton 패턴은

여러 객체들이 하나의 인스턴스를 공유한다는 개념은 같지만,

해당 인스턴스의 생명주기(생성, 사용, 소멸)에서 큰 차이를 보인다.

그 중에서 많이 문제를 일으킬만한 부분이 사용에 관한 생명주기 차이이다.

(Java static을 이용한 Singleton 패턴이란 이 링크에 있는 예제처럼 static 맴버변수를 이용해 레퍼런스를 공유하는 방식을 말합니다.)


Spring의 Singleton을 static 기반 Singleton과 착각하고 섞어 쓰려하는 경우가 있는데

이러지 말라고 얘기하기 위한 근거로써 두개가 어떻게 다른지 짚고 넘어가보자.


간단히 말하자면,

Java static의 공유 범위는 Classloader 기준이고,

Spring Singleton의 공유 범위는 ApplicationContext 기준이다.


위 두 문장을 단박에 이해할 수 있으면 더는 읽을 필요도 없고, 둘이 완전히 다르다는 것도 알테니 둘을 섞어쓰지도 않을거다..

뭐 차이는 알지만 무시하겠다는 사람은 어쩔 수 없지만서도...


자...그럼 Classloader 기준이라 하면 무엇이냐?


일단 이 글에서는 Tomcat7 기반으로 설명하겠다. (다른 컨테이너들도 동일할 수도 있겠으나... 레퍼런스는 톰캣 문서이다)


Classloader 자체가 뭔지는 설명하지 않는다.


많은 사람들이 JVM이 실행되면 하나의 Classloader로 모든 클래스를 로딩한다고 착각한다.

그런데 아래 그림을 보자

출처: http://tomcat.apache.org/tomcat-7.0-doc/class-loader-howto.html


이 그림은 Tomcat7의 classloader 구조도이다. 보면 알겠지만 Classloader를 계층적으로 관리한다. 각 계층별 역할은 출처 참조.

중요한건 Webapp1, Webapp2... 부분인데, 톰켓은 각 Web Application(쉽게 하나의 war덩어리)마다 classloader를 따로 사용한다.

달리말하면 서로 다른 war 파일에 속한 클래스들끼리는 서로 참조할 수 없다.

동일 class를 사용한다 하더라도 static 변수를 공유하지 않는다.

물론 상위 classloader에 있는 클래스는 하위 classloader에서 참조 가능하므로, Common에 static으로 선언되면 Webapp1과 Webapp2가 동일한 인스턴스를 참조한다.

참고: http://stackoverflow.com/questions/10712897/whats-the-scope-of-a-static-field


이것이 Java static 기반 Singleton의 사용 범위이다. 즉, Webapp 자신의 것 혹은 부모의 것.


그렇다면 Spring의 Singleton 사용 범위인 ApplicationContext 기준이란게 뭘까?


하나의 war 파일 안에는 web.xml에 등록된 여러개의 Servlet이 있을 수 있는데,

Spring은 DispatcherServlet이라는 Servlet을 사용한다.

당연히 DispatcherServlet은 여러개가 등록될 수 있으며, 그럴 경우 아래 그림과 같은 박스가 여럿 생기게 된다.


출처: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html


그림이 좀 애매하긴 한데...아래 참고 링크에도 있드시, 일반적으로 spring-mvc에서는

1. contextConfigLocation으로 지정해주는 root context

2. servlet으로 등록해주는 DispatcherServlet context

두 가지 정도가 있다.


참고: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html


그런데 이름으로도 알 수 있겠지만, DispatcherServlet은 각자 하나의 context를 갖게 되며,

여러 DispatcherServlet을 서블릿으로 등록해줄 경우 각각은 독립된 context를 구성하면서 서로간에 참조가 불가능해진다.

물론 classloader에서와 같이 상위 context인 root context는 서로 접근 가능하다.


살짝 용어가 애매해지기는 하는데...

Spring의 Singleton은 하나의 Spring IoC Container 안에서 하나만 생성되고 공유된다.

참고 링크의 5.5.1을 보면 내용이 나온다.


그럼 하나의 Spring IoC Container가 뭐냐?

문서 자체가 좀 모호하긴 하지만 하나의 ApplicationContext라고 보면 대충 맞다.

위 그림을 다시 보면, 하나의 DispatcherServlet 안에 여러 WebApplicationContext 들이 상호 참조를 한다.

web.xml에서 Root context 하나와 servlet context 여러개를 선언할 수 있다고 했다.

이 각각의 servlet context들이 Spring 기준의 Singleton 범위가 된다.


요즘은 하나의 web application에서 하나의 context만 만들어 쓰는게 유행인 듯 하지만(spring-boot는 아예 web.xml을 설정하지 않기 때문에 여러개의 서블릿을 띄울 일도 없다..기본적으로는)

아래 참고 링크에도 있다시피 고전적인 이유로 위와 같이 구분해서 사용할 수 있게 설계되어 있다.

참고: http://stackoverflow.com/questions/5132604/why-use-spring-applicationcontext-hierarchies


문제는 여기서부터인데,

Spring의 Singleton은 서로 다른 servlet context들 끼리는 참조가 불가능하다. classloader에서 처럼.

그런데 static 기반 Singleton은 서로 참조가 가능하다.


SharedBean 이라는 객체가 있고, servlet1, servlet2가 선언되어 있을 경우

servlet1과 servlet2에서 SharedBean을 각각 Singleton Bean으로 등록하면 서로 다른 인스턴스를 생성하지만

SharedBean의 static 맴버변수는 servlet1과 servlet2가 동일한 인스턴스를 참조한다.


바꿔말하면 servlet1에서 @Autowired를 통해 injection한 인스턴스를 public static 형태로 servlet2에서 접근할 경우

servlet2에서는 Null Pointer Exception을 뱉어낼 수도 있고, 의도하지 않은 bean들과 엮여있는 bean을 쓰게될 수도 있다.


그리고 근본적으로는

아래 링크와 같이 static 기반 Singleton이 그지같아서 Spring에서 그걸 개선하겠다고 IoC Container을 활용한 Singleton을 만들어준 것이다. 테스트 하나만 놓고봐도 static 기반 Singleton은 정말 그지같다. final static으로 발라놓은 클래스는 최적화 옵션에 따라 동작이 달라지기도 한다. 정말 테스트 하기 곤란하다.

링크: http://stackoverflow.com/questions/7253694/spring-how-to-inject-a-value-to-static-field


제발 문제가 많아서 개선책을 만들어줬는데 그걸 다시 문제많은 코드로 덮어쓰지 말자...단지 타이핑 좀 줄이겠다는 이유만으로...