Spring Boot Singleton VS Java static

최민길(Gale)·2023년 6월 12일
1

Spring Boot 적용기

목록 보기
23/46

안녕하세요 오늘은 Spring Boot의 빈을 주입하는 싱글톤 방식과 Java의 static 방식의 차이와 적용 범위에 대해서 알아보도록 하겠습니다.

결론부터 말씀드리자면, static의 경우 JVM 클래스 로더 기준으로, Spring Boot의 빈을 주입하여 객체를 참조하는 싱글톤 패턴은 ApplicationContext를 기준으로 적용됩니다. 이를 이해하기 위해선 Java의 실행 과정 및 Spring Boot의 동작 방식에 대해 알아보아야 합니다.

우선 Java의 동작 방식에 대해 알아보겠습니다. Java의 경우 컴파일 언어이기 때문에 일반적인 컴파일 언어의 동작 방식을 가지나 약간의 차이가 존재합니다. 대표적인 컴파일 언어인 C++의 경우 컴파일러가 목적 코드를 생성한 후 링커가 현재 OS에 맞는 실행 파일을 생성합니다. 반면 Java의 경우 컴파일러가 바이트 코드(클래스 파일)를 생성한 후 JVM에서 바이트 코드를 가져와 런타임 중 그때그때 기계어로 번역하여 동작합니다. 이런 차이점 때문에 C++의 경우 VM에 의존적이지 않은 대신 생성한 실행 파일이 OS 의존적이라 다른 OS에서 새롭게 컴파일해야한다는 단점이 존재하며, Java의 경우 JVM 위에서 동작하기 때문에 JVM에 의존적이나 어떤 OS에서도 환경에 맞는 JVM이 존재한다면 실행 가능하다는 장점이 있습니다.

그럼 이제 바이트 코드(클래스 파일)를 실행하는 JVM에 대해서 알아보겠습니다. JVM은 크게 클래스 로더, 실행 엔진, 가비지 콜렉터, 런타임 데이터 영역으로 나뉘어집니다. 위에서 언급했듯이, Java는 런타임 중 그때그때 바이트 코드를 번역하는 동적 로딩 방식을 채용하기 때문에 한번에 모든 클래스 파일을 가져오지 않습니다. 즉 현재 필요한 클래스 파일을 메모리(런타임 데이터 영역)으로 올리는 역할을 담당합니다.

클래스 로더는 위임 원칙이라는 특징이 있습니다. 아래에 보이는 그림처럼 클래스 로더는 필요한 클래스를 현재 클래스 로더에서 바로 가져오는 것이 아니라, 자신의 상위 클래스 로더에 필요한 클래스가 존재하는지 요청을 위임합니다. 즉 System Class Loader -> Extension Class Loader -> Bootstrap Class Loader 순서로 요청을 넘겨줍니다. 이후 Bootstrap Class Loader에 도달하면 요청한 클래스가 존재하는지 확인하고, 존재하지 않을 경우 하위 클래스 로더로 요청을 넘깁니다. 즉 Bootstrap Class Loader -> Extension Class Loader -> System Class Loader 순서로 요청을 넘기면서 하위 클래스 로더에 존재할 경우 메모리에 클래스를 로딩하고, 가장 하위인 System Class Loader에도 클래스가 존재하지 않는다면 ClassNotFoundException을 던지게 됩니다.

이어서 Spring Boot의 동작 방식에 대해 알아보겠습니다. 저번 포스팅에서 말씀드렸던 것처럼(https://velog.io/@gale4739/Spring-Boot-%EC%9D%B8%EC%A6%9D%EC%9D%B8%EA%B0%80-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EA%B3%A0%EB%8F%84%ED%99%94-%EC%9E%91%EC%97%85) Spring Boot는 요청을 서블릿 컨테이너에서 받아 어느 서블릿에 대한 요청인지 탐색 후 요청을 처리하여 결과를 전송하는 구조를 가지고 있습니다. 이 때 Spring Boot의 서블릿 컨테이너로는 기본적으로 내장 톰캣을 사용하게 됩니다. 따라서 톰캣의 동작 방식에 대한 이해가 필요합니다.

톰캣의 경우 다음과 같은 클래스 로더를 가집니다. 여기서 혼란스러운 부분이 발생합니다. JVM에서도 클래스 로더가 존재하는데 톰캣에서도 별도의 클래스 로더가 어떻게 존재하는가에 대한 부분입니다. 결론적으로 말씀드리자면 톰캣의 클래스 로더는 JVM과 다른 별도의 클래스 로더로 클래스를 로드하되, JVM의 클래스 로더에 의존적인 특징을 가집니다. 아래 그림은 톰캣의 클래스 로더 구조입니다. 톰캣의 클래스 로더 중 Bootstrap 클래스 로더의 경우 JVM의 기본 런타임 클래스와 System Extensions 디렉토리( $JAVA_HOME/jre/lib/ext)에 있는 JAR 파일의 모든 클래스가 포함되어 있습니다. 즉 톰캣은 JVM이 동작할 때, 즉 런타임 환경에서 동작하며 클래스 로더의 위임 특징에 따라 결국 JVM의 상위 클래스 로더에까지 요청을 전달하여 클래스를 참조하는 구조를 가집니다.

그렇다면 왜 JVM과 다른 별도의 클래스 로더를 가지고 있을까요? 그 이유는 각 서비스 별로 독립적인 메모리 참조를 위해서입니다. 아래 그림에서 Webapp1, Webapp2는 쉽게 설명하자면 각각 하나의 .jar(자바 아카이브, 자바 코드를 컴파일한 바이트 코드(클래스 파일) 및 관련 리소스들의 집합) 파일이 실행되는 클래스 로더입니다. 즉 서로 다른 서비스가 하나의 톰캣을 통해 요청을 주고받는다는 것을 의미합니다. 이 과정에서 만약 Webapp1 클래스 로더에서 정의된 클래스가 Webapp2 클래스 로더에서 참조가 가능하다면 객체 생성을 하나의 서비스에서 관리하지 않기 때문에 NullPointException 등 서비스의 불안정성이 향상됩니다. 따라서 톰캣은 서로 다른 서비스끼리 참조할 수 없으며, 이 때문에 Java의 static의 범위는 클래스 로더 단위, 즉 서비스 별로 Webapp1, Webapp2 단위로 한정되게 됩니다. 즉 Webapp1 클래스 로더에서 정의한 static 변수는 Webapp2 클래스 로더에서 참조할 수 없습니다. 물론 Common 클래스 로더에 존재하는 클래스는 두 클래스 로더 모두 참조 가능하므로 이 부분에 존재하는 static 변수를 모두 참조 가능합니다.

그렇다면 ApplicationContext를 기준으로 한다는 무슨 뜻일까요? 하나의 jar 파일에는 여러 개의 DispatcherServlet이 존재할 수 있습니다. 이 때 DispatcherServlet이란 HTTP 프로토콜로 들어오는 모든 요청을 가장 먼저 받아 적합한 컨트롤러에 위임해주는 프론트 컨트롤러의 역할을 담당합니다. 즉 요청한 URI가 어느 서블릿에 대한 요청인지를 탐색하여 적합한 서블릿으로 요청을 리다이렉트를 진행합니다. 이 때 각 DispathcerServlet마다 독립된 context를 구성하여 서블릿이 서로 다른 context끼리 참조가 불가능하게 됩니다. 따라서 Spring Boot의 싱글톤 방식의 범위는 ApplicationContext 범위, 즉 하나의 서블릿 내에서만 참조가 가능하다는 뜻이 됩니다.

정리하자면 Java의 static의 경우 클래스 로더 범위, 빈을 주입하는 싱글톤 패턴의 경우 ApplicationContext 단위로 static의 범위가 더 큰 것을 확인할 수 있습니다. 따라서 서로 다른 서블릿에서 같은 static 변수를 참조할 수 있기 때문에 한 서블릿에서 @Autowired를 통해 빈을 주입한 인스턴스를 다른 서블릿에서 static 형태로 참조하면 NullPointerException 또는 의도하지 않은 다른 빈들과 엮여 시스템 오류가 발생하게 됩니다.

하지만 스프링 부트의 경우 web.xml을 설정하지 않기 때문에 별도 설정을 하지 않는 이상 여러 개의 서블릿을 띄우지 않아 스프링 부트를 사용하는 중에서는 static과 @Autowired 사이의 간섭이 발생하지 않을 것으로 예측되지만, 이 부분은 추후 더 공부해서 확실하게 학습하도록 하겠습니다. 그럼 이상으로 포스팅 마치도록 하겠습니다!

출처
https://rollin.tistory.com/8097082
https://jungjim.tistory.com/69
https://coding-factory.tistory.com/827
https://steady-coding.tistory.com/593
https://stackoverflow.com/questions/19559210/how-tomcat-classloader-separates-different-webapps-object-scope-in-same-jvm
https://tomcat.apache.org/tomcat-8.5-doc/class-loader-howto.html
https://mangkyu.tistory.com/18

profile
저는 상황에 맞는 최적의 솔루션을 깊고 정확한 개념의 이해를 통한 다양한 방식으로 해결해오면서 지난 3년 동안 신규 서비스를 20만 회원 서비스로 성장시킨 Software Developer 최민길입니다.

0개의 댓글