본문 바로가기

JAVA/Spring

스프링3 싱글톤 레지스트리와 오브젝트 스코프

반응형

싱글톤 레지스트리와 오브젝트 스코프

DaoFactory를 직접사용하는 것과 @Configuration 애노테이션을 추가해서 스프링의 애플리케이션 컨텍스트를 통해 사용하는 것은 테스트 결과만 보면 동일한 것 같다.

하지만 스프링의 애플리케이션 컨텍스트는 기존에 직접 만들었던 오브젝트 팩토리와는 중요한 차이점있다.


오브젝트의 동일성과 동등성


차이점 예시)

1) DaoFactory를 직접사용하는 것

DaoFactory factory = new DaoFactory();

UserDao dao1 = factory.userDao();

UserDao dao2 = factory.userDao();


System.out.println(dao1);

System.out.println(dao2);


결과

UserDao@118f375

UserDao@117a8bd


2)@Configuration 애노테이션을 추가해서 스프링의 애플리케이션 컨텍스트

ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);

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

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


System.out.println(dao3);

System.out.println(dao4);


결과

UserDao@ee22f7

UserDao@ee22f7


두 오브젝트의 출력 값이 같으므로,  getBean()을 두번 호출해서 가져온 오브젝트가 동일하다

if(dao3 == dao4)은 true로 오브젝트 팩토리와 스프링 애플리케이션 컨텍스트의 동작방식의 차이점이 있다.


스프링은 여러번에 걸쳐 빈을 요청하더라도 매번 동일한 오브젝트를 돌려준다.


싱글톤 레지스트리로서의 애플리케이션 컨텍스트

애플리케이션 컨텍스트는 오브젝트 팩토리와 비슷한 방식으로 동작하는 IoC 컨테이너다.

그러면서 동시에 이 애플리케이션 컨텍스트는 싱글톤을 저장하고 관리하는 싱글톤 레지스트리이기도 하다.

스프링은 기본적으로 별다른 설정을 하지 않으면 내부에서 생성하는 빈 오브젝트를 모두 싱글톤으로 만든다.

(디자인 패턴에서 나오는 싱글톤 패턴과 비슷한 개념이지만 그 구현 방법은 확연히 다르다.)


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

스프링이 주로 적용되는 대상이 자바 엔터프라이즈 기술을 사용하는 서버환경이다.

스프링이 처음 설계됐던 대규모의 엔터프라이즈 서버환경은 서버 하나당 최대로 초당 수십에서 수백 번씩 브라우저나 여타 시스템으로부터의 요청을 받아 처리할 수 있는 높은 성능이 요구되는 환경이었다.

또 하나의 요청을 처리하기 위해 데이터 액세스 로직, 서비스로직, 비즈니스로직, 프레젠테이션 로직 등의 다양한 기능을 담당하는 오브젝트들이 참여하는 계층형 구조로 이뤄진 경우가 대부분이다. 비즈니스 로직도 복잡한 경우가 많다.

그런데 매번 클라이언트에서 요청이 올때마다 각 로직을 담당하는 오브젝트를 새로 만들어서 사용한다고 생각해보자.

요청 한번에 5개의 오브젝트가 새로 만들어지고 초당 500개의 요청이 들어오면, 초당 2500개의 새로운 오브젝트가 생성된다.

1분이면 십오만개, 한시간이면 9백만 개의 새로운 오브젝트가 만들어진다.

아무리 자바의 오브젝트 생성과 가비지 컬렉션GC의 성능이 좋아졌다고 하지만 이렇게 부하가 걸리면 서버가 감당하기 힘들다.

그래서 엔터프라이즈 분야에서는 서비스 오브젝트라는 개념을 일찍부터 사용해왔다.

서블릿은 자바 언터프라이즈 기술의 가장 기본이 되는 서비스 오브젝트라고 할 수 있다.

스펙에서 강제하지 않지만, 서블릿은 대부분 멀티스레드 환경에서 싱글톤으로 동작한다.

서블릿 클래스당 하나의 오브젝트만 만들어두고, 사용자의 요청을 담당하는 여러 스레드에서 하나의 오브젝트를 공유해 동시에 사용한다.

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

따라서 서버 환경에서는 서비스 싱글톤의 사용이 권장된다.

하지만 디자인 패턴에 소개된 싱글톤 패턴은 사용하기가 까다롭고 여러가지 문제점이 있다.

그래서 심지어 이런 싱글톤 패턴을 피해야 할 패턴이라는 의미로 안티패턴이라고 부르는 사람도 있다.


싱글톤패턴 Singleton pattern

디자인 패턴 중에서 가장 자주 활용되는 패턴이기도 하지만 가장 많이 비판 받는 패턴이기도 하다. 싱글톤 패턴은 매우 조심해서 사용해야 하거나 피해야할 패턴이라고 말하기도 한다.

싱글톤 패턴은 어떤 클래스를 애플리케이션 내에서 제한된 인스턴스 개수, 이름처럼 주로 하나만 존재하다록 강제하는 패턴이다.

이렇게 하나만 만들어지는 클래스의 오브젝트는 애플리케이션 내에서 전역적으로 접근이 가능하다. 단일 오브젝트만 존재해야하고, 이를 애플리케이션의 여러 곳에서 공유하는 경우에 주로 사용한다.


싱글톤 패턴의 한계

싱글톤 구하는 방법

1. 클래스 밖에서 오브젝트를 생성하지 못하도록 생성자를 private

2. 생성된 싱글톤 오브젝트를 저장할수 있는 자신과 같은 타입의 static 필드를 정의

3. static 팩토리 메서드인 getInstance()를 만들고 이 메소드가 최초로 호출되는 시점에서 한번만 오브젝트가 만들게 한다.

생성된 오브젝트는 static 필드에 저장한다. 또는 static 필드의 초기값으로 오브젝트를 미리 만들어둘 수도 있다.

4. 한번 오브젝트(싱글톤)이 만들어지고 난 후 에는 getIntance() 메서드를 통해 이미 만들어져 static 필드에 저장해둔 오브젝트를 넘겨준다.


public class UserDao {

private static UserDao INSTANCE;


private UserDao(ConnectionMaker connectionMaker) {

this.connectionMaker = connectionMaker;

}


public static synchronized UserDao getInstance() {

if(INSTANCE == null) INSTANCE = new UserDao(???);

return INSTANCE;

}

}


UserDao에 싱글톤을 위한 코드를 추가되고 나니 코드가 상당히 지저분해졌다는 느낌이 든다.

private으로 바뀐 생성자는 외부에서 호출할수가 없기 때문에 DaoFactory에서 UserDao를 생성하며 ConnectionMaker 오브젝트를 넣어주는게 이제는 불가능해졌다.


문제점

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

-싱글톤 패턴은 생성자를 private으로 제한한다.

오직 싱글톤 클래스 자신만이 자기 오브젝트를 만들도록 제한한다.

private 생성자를 가진 클래스는 다른 생성자가 없다면 상속이 불가능하다는 점이다.

객체지향의 장점인 상속과 이를 이용한 다형성을 적용할 수 없다.

기술적인 서비스만 제공하는 경우는 괜찮지만, 애플리케이션의 로직을 담고 있는 일반 오브젝트의 경우 싱글톤으로 만들었을 때 객체지향적인 설계의 장점을 적용하기가 어렵다는 점이다.

상속과 다형성 같은 객체지향의 특징이 적용되지 않는 static 필드와 메서드를 사용하는 것도 역시 동일한 문제를 발생시킨다.


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

-싱글톤은 테스트하기가 어렵거나 테스트 방법에 따라 아예 테스트가 불가능하다.

싱글톤은 만들어지는 방식이 제한적이기 때문에 테스트에서 사용될 목 오브젝트 등으로 대체하기 힘들다.

싱글톤은 초기화 과정에서 생성자 등을 통해 사용할 오브젝트를 다이나믹하게 주입하기도 힘들기 때문에 필요한 오브젝트는 직접 오브젝트를 만들어 사용할 수밖에 없다. 이런 경우 테스트용 오브젝트로 대체하기 힘들다.


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

- 서버에서 클래스 로더를 어떻게 구성하고 있느냐에 따라서 싱글톤 클래스임에도 하나 이상의 오브젝트가 만들어질 수 있다.

따라서 자버 언어를 이용한 싱글톤 패턴 기법은 서버환경에서는 싱글톤이 꼭 보장된다고 볼수 없다.

여러 개의 JVM에서 분산돼서 설치가 되는 경우에도 각각 독립적으로 오브젝트가 생긱기 때문에 싱글톤으로서의 가치가 떨어진다.


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

- 싱글톤은 사용하는 클라이언트가 정해져 있지 않다.

싱글톤의 static 메서드를 이용해 언제든지 싱글톤에 쉽게 접근할 수 있기 때문에 애플리케이션 어디서든지 사용될 수 있고, 그러다 보면 자연스럽게 전역 상태(global state)로 사용되기 쉽다.

아무객체나 자유롭게 접근하고 수정하고 공유할 수 있는 전역 상태를 갖는 것은 객체지향 프로그래밍에서는 권장되지 않는 프로그래밍 모델이다.

그럼에도 싱글톤을 사용하면 그런 유혹에 빠지기 쉽다. 그럴바에 아예 static 플드와 메서드로만 구성된 클래스를 사용하는 편이 낫다.


싱글톤 레지스트리

스프링은 서버환경에서 싱글톤이 만들어져서 서비스 오브젝트 방식으로 사용되는 것을 지지한다.

자바의 기본적인 싱글톤 패턴의 구현방식은 여러 가지 단점이 있기 때문에, 스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공한다. 

스프링 컨테이너는 싱글톤을 생성, 관리, 공급하는 싱글톤 관리 컨테이너이기도 하다.


장점)

static 메서드와 private 생성자를 사용하지 않고 자바 클래스를 싱글톤으로 활용하게 해준다.

public 생서자를 가질 수 있다.

테스트 환경에 자유롭게 오브젝트를 만들수 있고, 테스트를 위한 목 오브젝트로 대체하는 것도 간단하다.

생성자 파라미터를 이용해서 사용할 오브젝트를 넣어줄수 있다.


객체지향적인 설계 방식과 원칙, 디자인 패턴(싱글톤패턴은 제외)등을 적용하는데 아무런 제약이 없다.

스프링이 빈을 싱글톤으로 만드는 것은 결국 오브젝트의 생성방법을 제어하는 IoC 컨터에너로서의 역할이다.


싱글톤과 오브젝트의 상태

싱글톤은 멀티스레드 환경이라면 여러 스레드가 동시에 접근해서 사용할 수 있다.

따라서 상태 관리에 주의를 기울여야 한다.

기본적으로 싱글톤이 멀티스레드 환경에서 버시스 형태의 오브젝트로 사용되는 경우에는 상태정보를 내부에 갖고 있지 않은 무상태(stateless)방식으로 만들어져야 한다.

다중 사용자의 요청을 한꺼번에 처리하는 스레드들이 동시에 싱글톤 오브젝트의 인스턴스 변수를 수정하는 것은 매우 위험하다.

저장할 공간이 하나뿐이니 서로 값을 덮어쓰고 자신이 저장하지 않은 값을 읽어올 수 있기 때문이다.

따라서 싱글톤은 기본적으로 인스턴스 필드의 값을 변경하고 유지하는 상태유지(stateful)방식으로 만들지 않는다.

이를 지키지 않으면 개발자 혼자서 개발하고 테스트 할때는 아무런 문제가 없겠지만, 서버에 배포되고 여러 사용자가 동시에 접속하면 데이터가 엉망이 돼버리는 등 심각한 문제가 발생할 것이다.

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

상태가 없는 방식으로 클래스를 만드는 경우에 각 요청에 대한 정보나, DB나 서버의 리소스로부터 생성한 정보는 어덯게 다뤄야 할까?

이때 파라미터와 로컬 변수, 리턴 값 등을 이용하면된다.

메서드 파라미터나 메서드 안에서 생성되는 로컬 변수는 매번 새로운 값을 저장할 독립적인 공간이 만들어지기 때문에 싱글톤이라고 해도 여러 스레드가 변수의 값을 덮어쓸일은 없다.


public class UserDao {

private ConnectionMaker connectionMaker; //초기에 설정하면 사용중에는 바뀌지 않는 읽기 전용 인스턴스 변수

private Connection c; //매번 새로운 값으로 바뀌는 정보를 담은 인스턴스 변수

private USer user; //심각한 문제가 발생


public User get(String id) throws ClassNotFoundException, SQLException {

this.c = connectionMaker.makeConnection();


this.user = new User();

this.user.setId(rs.getString("id"));

this.user.setName(rs.getString("name"));

this.user.setPassword(rs.getString("password"));


return this.user;

}

}


기존에 만들었던 UserDao와 다른점은 기존에 로컬 변수로 선언하고 사용했던 Connection과 User를 클래스의 인스턴스 필드로 선언했다는 것이다.


따라서 싱글톤으로 만들어져서 멀티스레드 환경에서 사용하면 위에 설명대로 심각한 문제가 발생한다.

따라서 스프링의 싱긑톤 빈으로 사용되는 클래스를 만들 때는 기존의 UserDao처럼 개별적으로 바뀌는 정보는 로컬변수로 정의하거나, 파라미터로 주고받으면서 사용하게 해야 한다.

그런데 기존의 UserDao에서도 인스턴스 변수로 정의해서 사용한 것이다.

바로 ConnectionMaker 인터페이스 타입의 connectionMaker다.

이것은 인스턴스 변수를 사용해도 상관없다.

왜냐하면 connectionMaker는 읽기 전용의 정보이기 때문이다.

이 변수에는 ConnectionMaker 타입의 싱글톤 오브젝트가 들어있다.

이 connectionMaker도 DaoFactory에 @Bean을 붙여서 만들었으니 스프링이 관리하는 빈이 될것이고, 별다른 설정이 없다면 기본적으로 오브젝트 한 개만 만들어져서 UserDao의 connectionMaker 인스턴스 필드에 저장된다.

일허게 자신이 사용하는 다른 싱글톤 빈을 저장하려는 용도라면 인스턴스 변수를 사용해도 좋다.

스프링이 한번 초기화해주고 나면 이후에는 수정되지 않기 때문에 멀티스레드 환경에서 사용해도 아무런 문제가 없다.

동일하게 읽기전용의 속성을 가진 정보라면 싱글톤에서 인스턴스 변수로 사용해도 좋다.

물론 단훈한 읽기전용 값이라면 static final이나 final로 선언하는 편이 나을 것이다.


스프링 빈의 스코프

스프링이 관리하는 오브젝트, 즉 빈이 생성되고 존재하고 적용되는 범위을 빈의 스코프(scope)라고 한다.

스프링 빈의 기본 스코프는 싱글톤이다.

싱글톤 스코프는 컨테이너 내에 한 개의 오브젝트만 만들어져서, 강제로 제거하지 않는 이상 한 스프링 컨테이너가 존재하는 동안 계속 유지된다.

스프링에서 만들어지는 대부분의 빈 싱글톤 스코프를 갖는다.

경우에 따라서는 싱글톤 외의 스코프를 가질 수 있다.

대표적으로 프로토타입(prototype) 스코프가 있다.

프로토타입은 싱글톤과 달리 컨테이너에 빈을 요청할 때마다 매번 새로운 오브젝트를 만들어준다.

그외에도 웹을 통해 새로운 HTTP 요청이 생길때마다 생성되는 요청(request) 스코프가 있고, 웹의 세션과 스코프가 유사한 세선(session) 스코프도 있다.

스프링에서 만들어지는 빈의 스코프는 싱글톤 외에도 다양한 스코프를 사용할 수 있다.



반응형

'JAVA > Spring' 카테고리의 다른 글

스프링 3 의존관계 주입(DI)의 응용  (0) 2018.03.15
스프링 3 의존관계 주입(DI)  (0) 2018.03.13
스프링3 제어의 역전(IoC)  (0) 2018.03.07
스프링3 개론  (0) 2018.03.05
Spring Boot  (0) 2017.12.19