Application Context와 Bean Scope

초코칩·2024년 4월 17일
0

spring

목록 보기
5/10
post-thumbnail

싱글톤 레지스트리로서의 Application Context

Application Context는 오브젝트 팩터리와 비슷한 방식으로 작동하는 IoC 컨테이너이면서 싱글톤을 저장하고 관리하는 싱글톤 레지스트리이기도 하다.

오브젝트 팩터리와 Application Context를 통해 사용하는 것은 동일한 결과를 반환하는 것처럼 보인다.

하지만 실제 오브젝트 팩터리는 매번 다른 객체를 생성하고 Application Context는 같은 객체(동일성)가 반환된다.

ApplicationContext context = new AnnotationConfigApplication(DaoFactory.class);

UserDao dao3 = context.getBean("userDao", UserDao.class);
UserDao dao4 = context.getBean("userDao", UserDao.class);

System.out.println(dao3);	//UserDao@ee22f7
System.out.println(dao4);	//UserDao@ee22f7

즉, 스프링은 기본적으로 내부적으로 생성하는 Bean 오브젝트를 모두 싱글톤으로 관리한다는 것이다.

서버 애플리케이션과 싱글톤

왜 스프링은 싱글톤으로 Bean을 만드는 것일까? 이는 스프링이 주로 적용되는 대상이 자바 엔터프라이즈 기술을 사용하는 서버환경이기 때문이다.

스프링이 처음 설계됐던 대규모의 엔터프라이즈 서버환경은 서버 하나당 최대로 초당 수십에서 수백 건씩 브라우저나 여타 시스템으로부터의 요청을 받아 처리할 수 있는 높은 성능이 요구되었다. 매번 클라이언트에서 요청이 올 때마다 각 로직을 담당하는 오브젝트를 새로 만들어서 사용하면 서버가 감당하기 힘들다.

서비스 오브젝트

엔터프라이즈 분야에서는 서비스 오브젝트라는 개념을 일찍부터 사용해왔다. 그 중 서블릿은 자바 엔터프라이즈 기술의 가장 기본이 되는 서비스 오브젝트이다. 이러한 서블릿은 대부분 멀티스레드 환경에서 싱글톤으로 동작한다. 서블릿 클래스당 하나의 오브젝트만 만들어두고, 사용자의 요청을 담당하는 여러 스레드에서 하나의 오브젝트를 공유해 동시에 사용한다.

이렇게 애플리케이션 안에 제한된 수, 대개 한 개의 오브젝트만 만들어서 사용하는 것이 싱글톤 패턴의 원리다.

싱글톤 패턴의 한계

자바에서 싱글톤을 구현하는 방법은 다음과 같다.

  • 클래스 밖에서 오브젝트를 생성하지 못하도록 private으로 만든다.
  • 생성된 싱글톤 오브젝트를 저장할 수 있는 자신과 같은 타입의 static 필드를 정의한다.
  • static 팩터리 메서드인 getInstance()를 만들고 이 메서드가 최초로 호출되는 시점에서 한번만 오브젝트가 만들어지게 한다. 생성된 오브젝트는 static 필드에 저장된다.
  • 한번 오브젝트가 만들어지고 난 후에는 getInstance() 메서드를 통해 이미 만들어져 static 필드에 저장해둔 객체를 넘긴다.

하지만 싱글톤 패턴 구현 방식에는 다음과 같은 문제가 있다.

private 생성자를 갖고 있기 때문에 상속할 수 없다

싱글톤 패턴은 생성자를 private으로 제한한다. 오직 싱글톤 클래스 자신만이 오브젝트를 만들도록 제한하는 것이다. 문제는 private 생성자를 가진 클래스는 다른 생성자가 없다면 상속이 불가능하다. 따라서 객체지향의 장점인 상속과 이를 이용한 다형성을 적용할 수 없다.

싱글톤은 테스트하기가 힘들다

싱글톤은 테스트하기가 어렵거나 테스트 방법에 따라 아예 불가능하다. 싱글톤은 만들어지는 방식이 제한적이기 때문에 테스트에서 사용될 때 Mock 객체로 대체하기 힘들다. 뿐만 아니라 초기화 과정에서 생성자 등을 통해 사용할 오브젝트를 다이나믹하게 주입하기도 어렵기 때문에 필요한 오브젝트는 직접 만들어 사용해야 한다.

서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다

서버에서 클래스 로더를 어떻게 구성하고 있느냐에 따라서 싱글톤 클래스임에도 하나 이상의 오브젝트가 만들어질 수 있다. 따라서 자바 언어를 이용한 싱글톤 패턴 기법은 서버 환경에서는 싱글톤이 꼭 보장된다고 할 수 없다. 여러 개의 JVM에 분산돼서 설치가 되는 경우에도 각각 독립적인 객체가 생기기 때문에 싱글톤의 가치가 떨어진다.

싱글톤 사용은 전역 상태를 만들 수 있기 때문에 바람지하지 못하다

싱글톤의 static 메서드를 이용해 언제든지 싱글톤에 쉽게 접근할 수 있기 때문에 애플리케이션 어디서든지 전역 상태로 사용될 수 있다. 아무 객체나 자유롭게 접근하고 수정하고 공유할 수 있는 전역 상태를 갖는 것은 객체지향 프로그래밍에서는 권장되지 않는다.

싱글톤 레지스트리(singleton registry)

위에서 언급한 자바의 기본적인 싱글톤 패턴의 구현 방식은 여러 가지 단점이 있기 때문에, 스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공한다. 그것이 바로 싱글톤 레지스트리(singleton registry)다. 스프링 컨테이너는 싱글톤 Bean 객체를 생성하고, 관리하고, 공급하는 싱글톤 관리 컨테이너이기도 하다.

싱글톤 레지스트리의 장점은 static 메소드와 private 생성자를 사용해야 하는 비정상적인 클래스가 아니라 평범한 자바 클래스를 싱글톤으로 활용하게 해준다는 점이다. 평범한 자바 클래스라도 IoC 방식의 컨테이너를 사용해서 생성과 관계설정, 사용 등에 대한 제어권을 컨테이너에게 넘기면 손쉽게 싱글톤 방식으로 만들어져 관리되게 할 수 있다. 오브젝트 생성에 관한 모든 권한은 IoC 기능을 제공하는 애플리케이션 컨텍스트에게 있기 때문이다. 스프링의 싱글톤 레지스트리 덕분에 싱글톤 방식으로 사용될 애플리케이션 클래스라도 public 생성자를 가질 수 있다. 싱글톤으로 사용돼야 하는 환경이 아니라면 간단히 오브젝트를 생성해서 사용할 수 있다. 따라서 테스트 환경에서 자유롭게 오브젝트를 만들 수 있고, 테스트를 위한 목 오브젝트로 대체하는 것도 간단하다. DaoFactory에서 UserDao에 ConnectionMaker 오브젝트를 사용하도록 관계를 설정해주듯이, 생성자 파라미터를 이용해서 사용할 오브젝트를 넣어주게 할 수도 있다.

가장 중요한 것은 싱글톤 패턴과 달리 스프링이 지지하는 객체지향적인 설계 방식과 원칙, 디자인 패턴(싱글톤 패턴은 제외) 등을 적용하는 데 아무런 제약이 없다는 점이다. 스프링은 IoC 컨테이너일 뿐만 아니라, 고전적인 싱글톤 패턴을 대신해서 싱글톤을 만들고 관리해주는 싱글톤 레지스트리라는 점을 기억해두자. 스프링이 Bean을 싱글톤으로 만드는 것은 결국 오브젝트의 생성 방법을 제어하는 IoC 컨테이너로서의 역할이다. 그래서 getBean()을 여러 번 호출해서 Bean을 요청하더라도 매번 동일한 오브젝트를 받게 된다. 만약 스프링 없이 DaoFactory만 사용한다면 이렇게 싱글톤 방식으로 UserDao를 한 번만 만들어두고 매번 같은 오브젝트를 리턴하게 하려면 DaoFactory가 상당히 지저분해질 것이다.

다음은 싱글톤으로 만들어지기 때문에 주의해야 할 점에 대해 알아보자.

싱글톤과 오브젝트의 상태

싱글톤은 멀티스레드 환경이라면 여러 스레드가 동시에 접근해서 사용할 수 있다. 따라서 상태 관리에 주의해야 한다. 기본적으로 싱글톤이 멀티스레드 환경에서 서 비스 형태의 오브젝트로 사용되는 경우에는 상태정보를 내부에 갖고 있지 않은 무상태 (stateless) 방식으로 만들어져야 한다. 다중 사용자의 요청을 한꺼번에 처리하는 스레드들 이 동시에 싱글톤 오브젝트의 인스턴스 변수를 수정하는 것은 매우 위험하다. 저장할 공간이 하나뿐이니 서로 값을 덮어쓰고 자신이 저장하지 않은 값을 읽어올 수 있기 때문이다. 따라서 싱글톤은 기본적으로 인스턴스 필드의 값을 변경하고 유지하는 상태유지(stateful) 방식으로 만들지 않는다. 이를 지키지 않으면 개발자 혼자서 개발하고 테스트 할 때는 아무런 문제가 없겠지만, 서버에 배포되고 여러 사용자가 동시에 접속하면 데이터가 엉망이 돼버리는 등의 심각한 문제가 발생할 것이다.

물론 읽기전용의 값이라면 초기화 시점에서 인스턴스 변수에 저장해두고 공유하는 것은 아무 문제가 없다.

상태가 없는 방식으로 클래스를 만드는 경우에 각 요청에 대한 정보나, DB나 서버 의 리소스로부터 생성한 정보는 어떻게 다뤄야 할까? 이때는 파라미터와 로컬 변수, 리 턴 값 등을 이용하면 된다. 메소드 파라미터나, 메소드 안에서 생성되는 로컬 변수는 매번 새로운 값을 저장할 독립적인 공간이 만들어지기 때문에 싱글톤이라고 해도 여러 스레드가 변수의 값을 덮어쓸 일은 없다.

ReadOnly 객체

ReadOnly의 특성을 가진 객체는 인스턴스 변수로 사용해도 무관하다. ReadOnly 객체를 @Bean으로 만들려는 의도였다면 인스턴스 변수로 저장해도 좋다.

스프링 Bean Scope

스프링이 관리하는 오브젝트인 Bean이 생성되고, 존재하고, 적용되는 범위를 결정하는 것을 Bean Scope라 한다.

  • Singleton: 기본적으로 모든 Spring Bean은 Singleton Scope이다. 이는 애플리케이션 컨텍스트에 단일 인스턴스만 생성되고, 이 인스턴스가 애플리케이션 전체에서 공유됨을 의미한다.
  • Prototype: Prototype Scope를 갖는 Bean은 요청될 때마다 새로운 인스턴스가 생성된다. 각 요청에 대해 별도의 Bean 인스턴스가 생성되므로 상태가 공유되지 않는다.
  • Request Scope: 각 HTTP 요청마다 새로운 인스턴스가 생성되며, 그 요청이 완료될 때까지 유지된다.
  • Session Scope: 각 HTTP 세션마다 새로운 인스턴스가 생성되며, 그 세션이 종료될 때까지 유지된다.
  • Application Scope: Application Scope는 ServletContext 범위 내에서 하나의 Bean 인스턴스를 생성하고, 애플리케이션 전체에서 유지된다.
  • WebSocket: 하나의 Bean 정의를 WebSocket의 라이프사이클에 대응시킨다. Web-aware한 Spring ApplicationContext에서만 유효하다.

Ref

profile
초코칩처럼 달콤한 코드를 짜자

0개의 댓글