오브젝트의 동일성과 동등성
자바에서 두 개의 오브젝트가 같은가라는 말은 주의해서 써야한다. 자바에서는 두 개의 오브젝트가 완전히 같은 동일한(identical) 오브젝트라고 말하는 것과, 동일한 정보를 담고 있는(equivalent) 오브젝트라고 말하는 것은 분명한 차이가 있다. 전자는 동일성(identity)비교라고 하고, 후자를 동등성(equality) 비교라고 한다. 동일성은 == 연산자로, 동등성은 equals() 메소드를 이용해 비교한다.
두 개의 오브젝트가 동일하다면 사실은 하나의 오브젝트만 존재하는 것이고 두 개의 오브젝트 레퍼런스 변수를 갖고 있을 뿐이다. 두 개의 오브젝트가 동일하지는 않지만 동등한 경우에는 두 개의 각기 다른 오브젝트가 메모리상에 존재하는 것인데, 오브젝트의 동등성 기준에 따라 두 오브젝트의 정보가 동등하다고 판단하는 것일 뿐이다. 물론 동일한 오브젝트는 동등하기도 할 것이다. 하지만 그 반대는 항상 참은 아니다.
자바 클래스를 만들 때 equals() 메소드를 따로 구현하지 않았다면, 최상위 클래스인 Object 클래스에 구현되어 있는 equals() 메소드가 사용된다. Object의 equals() 메소드는 두 오브젝트의 동일성을 비교해서 그 결과를 돌려준다. 따라서 이때는 동일한 오브젝트여야지만 동등한 오브젝트라고 여겨질 것이다.
기존에 만들었던 오브젝트 팩토리와 애플리케이션 컨텍스트의 차이점은
오브젝트 팩토리는 매번 새로운 인스턴스를 생성한다는 것이고 애플리케이션 컨텍스트는 하나의 오브젝트의 새로운 레퍼런스 변수를 생성한다는 것이다.
Example Code
직접 생성한 DaoFactory 오브젝트 출력 코드
DaoFactory factory = new DaoFactory();
UserDao dao1 = factory.userDao();
UserDao dao2 = factory.userDao();
System.out.println(dao1);
System.out.println(dao2);
/*
실행결과
springbook.user.domain.UserDao@3caeaf62
springbook.user.domain.UserDao@e6ea0c6
*/
스프링 컨텍스트로부터 가져온 오브젝트 출력 코드
ApplicationContext context = new AnnotationConfigApplicationContext(DaoFactory.class);
UserDao dao3 = context.getBean("userDao", UserDao.class);
UserDao dao4 = context.getBean("userDao", UserDao.class);
System.out.println(dao3);
System.out.println(dao4);
/*
실행결과
springbook.user.domain.UserDao@47caedad
springbook.user.domain.UserDao@47caedad
*/
동일한 hashcode가 출력된다. 즉 여러 번에 걸쳐 빈을 요청하더라도 매번 동일한 오브젝트를 돌려준다는 것이다.
왜 그럴까?
1. 싱글톤 레지스토리로서의 애플리케이션 컨텍스트
애플리케이션 컨텍스트는 오브젝트 팩토리와 비슷한 방식으로 동작하는 IoC 컨테이너다.
동시에 싱글톤을 저장하고 관리하는 싱글톤 레지스트리(singleton registry)이기도 하다.
싱글톤의 개념과 한계점, 자바에서 싱글톤을 구현하는 방법에 대해서 다룹니다.
서버 애플리케이션과 싱글톤
스프링이 주로 적용되는 대상이 자바 엔터브라이즈 기술을 사용하는 서버환경이기 때문에 싱글톤으로 빈을 만듬
서버 하나당 최대로 초당 수십에서 수백 번씩 브라우저나
여타 시스템으로부터 요청을 받아 처리할 수 있는 높은 성능이 요구되는 환경
요청 한번에 5개의 오브젝트 -> 초당 500개의 요청 ->
초당 2500개 오브젝트 -> 1분 150,000 -> 1시간 9,000,000 -> 높은 부하
그로인해 엔터프라이즈 분야에서는 서비스 오브젝트라는 개념을 사용했다.
서블릿은 자바 엔터프라이즈 기술의 가장 기본이 되는 서비스 오브젝트이다.
서블릿은 대부분 멀티스레드 환경에서 싱글톤으로 동작한다. 서블릿 클래스당 하나의 오브젝트만 만들어두고, 사용자의 요청을 담당하는 여러 스레드에서 하나의 오브젝트를 공유해 동시에 사용한다.
이렇게 애플리케이션 안에서 제한된 수, 대개 한 개의 오브젝트만 만들어서 사용하는 것이 싱글톤 패턴의 원리다.
싱글톤 패턴
싱글톤 패턴은 GoF가 소개한 디자인 패턴 중의 하나다. 디자인 패턴 중에서 가장 자주 활용되는 패턴이자 가장 많은 비판을 받는 패턴이다.
심지어 저자인 GoF 멤버조차도 싱글톤 패턴은 매우 조심해서 사용해야 하거나 피해야 할 패턴이라고 말하기도 한다.
싱글톤 패턴은 어떤 클래스의 애플리케이션 내에서 제한된 인스턴스 개수, 이름처럼 주로 하나만 존재하도록 강제하는 패턴이다. 이렇게 하나만 만들어지는 클래스의 오브젝트는 애플리케이션 내에서 전역적으로 접근이 가능하다. 단일 오브젝트만 존재해야 하고, 이를 애플리케이션의 여러 곳에서 공유하는 경우에 주로 사용한다.
싱글톤 패턴의 한계
자바에서 싱글톤을 구현하는 방법
- 클래스 밖에서는 오브젝트를 생성하지 못하도록 생성자를 private로 만듬
- 생성된 싱글톤 오브젝트를 저장할 수 있는 자신과 같은 타입의 스태틱 필드를 정의
- 스태틱 팩토리 메소드인 getInstance()를 만들고 이 메소드가 최초로 호출되는 시점에서 한번만 오브젝트가 만들어지게 한다. 생성된 오브젝트는 스태틱 필드에 저장된다. 또는 스태틱 필드의 초기값으로 오브젝트를 미리 만들어 둘 수 도 있다.
- 한번 오브젝트(싱글톤)가 만들어지고 난 후에는 getInstance() 메서드를 통해 이미 만들어져 스태틱 필드에 저장해둔 오브젝트를 넘겨준다.
싱글톤 패턴을 적용한 UserDao
Example Code
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
}
}
싱글톤 패턴 구현 방식의 문제점
- private 생성자를 갖고 있기 때문에 상속할 수 없다.
- 상속과 다형성 이용 불가능
- 일반 오브젝트의 경우 객체지향적인 설계의 장점을 적용하기 어려움
- 상속과 다형성 같은 객체지향의 특징이 적용되지 않은 스태틱 필드와 메소드를 사용하는 것도 동일한 문제 발생시킴
- 싱글톤은 테스트하기가 힘들다.
- 초기화 과정에서 생성자 등을 통해 사용할 오브젝트를 다이나믹하게 주입하기도 힘들기 때문에 필요한 오브젝트를 직접 만들어 사용해야 함
- 서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다.
- 클래스 로더를 어떻게 구성하고 있느냐에 따라 싱글톤 클래스임에도 하나 이상의 오브젝트가 만들어질 수 있음
- 여러 개의 JVM에 분산돼서 설치가 되는 경우에도 각각 독립적인 오브젝트가 생겨 싱글톤의 가치가 떨어짐
- 싱글톤의 사용은 전역 상태를 만들 수 있기 때문에 바람직하지 못하다.
- 싱글톤은 사용하는 클라이언트가 정해져 있지 않다.
- 싱글톤의 스태틱 메소드를 이용해 언제든지 싱글톤에 쉽게 접근 가능 할수 있어 전역 상태(global state)로 사용되기 쉬움
- 아무 객체나 자유롭게 접근, 수정, 공유할 수 있는 전역 상태는 객체지향 프로그램에서 지양해야함
싱글톤 레지스트리
- 스프링이 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공하는 것
- 스태틱 메소드와 private 생성자를 사용해야 하는 비정상적 클래스가 아닌 평범한 클래스도 싱글톤 가능
- 목 오브젝트 대체 및 객체지향적 특성, 디자인 패턴 등을 모두 활용하며 싱글톤의 장점도 활용
2. 싱글톤과 오브젝트의 상태
- 멀티스레드 환경에서 서비스 형태의 오브젝트로 사용되는 경우 상태정보를 내부에 갖고 있지 않는 무상태(stateless)방식으로 만들어야 한다.
- 다중 사용자의 요청을 한꺼번에 처리하는 스레드들이 동시에 싱글톤 오브젝트 변수를 수정하는 것은 위험
- 저장 공간이 하나뿐이니 서로 값을 덮어쓰고 자신이 저장하지 않은 값을 읽어올 수 있음
- 따라서 인스턴스 필드의 값을 변경하고 유지하는 상태유지(stateful) 방식으로 만들지 않음
- 읽기 전용의 값이라면 초기화 시점에서 인스턴스 변수를 저장해두고 공유하는 것은 문제가 없음
- 파라미터, 로컬 변수, 리턴 값등을 이용해 stateless 클래스의 생성 정보를 다룸
- 메소드 파라미터나 메소드 안에서 생성되는 로컬 변수는 매번 새로운 값을 저장할 독립적인 공간 생성하기에 위험없음
Example Code
public class UserDao {
// 초기에 설정하면 사용 중에 바뀌지 않는 읽기전용 인스턴스 변수
private ConnectionMaker connectionMaker;
private Connection c; // 매번 새로운 값으로 바뀌는 정보를 담은 인스턴스 변수
private User user; // 매번 새로운 값으로 바뀌는 정보를 담은 인스턴스 변수
public User get(String id) throws ClassNotFoundException, SQLException {
Connection 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처럼 개별적으로 바뀌는 정보는 로컬 변수로 정의하거나, 파라미터로 주고 받으면서 사용 해야 함
ConnectionMaker는 읽기전용의 정보이기 때문에 인스턴스 변수로 정의해 사용해도 무방
3. 스프링 빈의 스코프
스프링이 관리하는 오브젝트, 즉 빈이 생성되고, 존재하고, 적용되는 범위에 대해 알아보자.
스프링에서는 이것을 빈의 스코프(scope)라고 한다. 스프링 빈의 기본 스코프는 싱글톤이다.
싱글톤 스코프는 컨테이너 내에 한 개의 오브젝트만 만들어져서, 강제로 제거하지 않는 한 스프링 컨테이너가 존재하는 동안 계속 유지된다. 스프링에서 만들어지는 대부분의 빈은 싱글톤 스코프를 갖는다.
경우에 따라 싱글톤 외으 스코프를 가지는데 대표적으로 프로토타입(prototype) 스코프가 있다.
프로토타입은 싱글톤과 달리 컨테이너에 빈을 요청할 때마다 매번 새로운 오브젝트를 만들어준다. 그 외에도 웹을 통해 새로운 HTTP 요청이 생길때마다 생성되는 요청(requeset) 스코프가 있고, 웹의 세션과 스코프가 유사한 세션(session) 스코프도 있다. 스프링에서 만들어지는 빈의 스코프는 싱글톤 외에도 다양한 스코프를 사용할 수 있다.
싱글톤 외의 빈 스코프에 대해서는 10장에서 다뤄보자.
'BackEnd > Spring & Springboot Study' 카테고리의 다른 글
[토비의 스프링] 1.8 XML을 이용한 설정 (0) | 2022.12.08 |
---|---|
[토비의 스프링] 1.7 의존관계 주입(DI) (0) | 2022.12.04 |
[토비의 스프링] 1.5 스프링의 Ioc (0) | 2022.12.02 |
[토비의 스프링] 1.4 제어의 역전(IoC) (0) | 2022.12.01 |
[토비의 스프링] 1.3 DAO의 확장 (0) | 2022.11.29 |