공부해봅시당
[스프링] 오브젝트와 의존관계 6 본문
1.6. 싱글톤 레지스트리와 오브젝트 스코프
DaoFactory를 직접 사용하는 것과 @Configuration 어노테이션을 추가해서 스프링의 어플리케이션 컨텍스트를 통해 사용하는 것은 테스트 결과만 보자면 동일한 것 같음
하지만 스프링의 애플리케이션 컨텍스트는 기존에 직접 만들었던 오브젝트 팩토리와는 중요한 차이점이 있음
오브젝트의 동일성와 동등성
1. 동일성(identity) 비교
1) 비교 방법 : == 연산자
2) 의미 : 두 개의 오브젝트가 동일하다면 사실은 하나의 오브젝트만 존재, 두 개의 오브젝트 레퍼런스 변수를 가짐
(래퍼런스와 오브젝트의 차이는 아래 링크 참조)
2. 동등성(equivalent) 비교
1) 비교 방법 : equals() 메소드
if) equals() 메소드 따로 구현하지 않은 경우
- 최상위 클래스인 Object 클래스에 구현되어 있는 equals() 메소드 사용
: 두 오브젝트의 동일성을 비교해 그 결과를 돌려줌 -> 동일한 오브젝트여야 동등한 오브젝트라고 여겨짐
2) 의미 : 두 개의 각기 다른 오브젝트가 메모리상에 존재, 동등성 기준에 따라 두 오브젝트의 정보가 동등하다고 판단
3. 동일성과 동등성의 상관관계
1) 동일한 오브젝트는 동등함
2) 동등한 오브젝트라고 해서 항상 동일하지는 않음
[자바] Reference와 Object의 차이
Reference와 Object Object는 Class의 인스턴스로 특정 메모리 슬롯에 저장됩니다. Class는 Object를 어떻게 생성해야하는지 설명되어 있는 템플릿 같은 것 입니다. Reference는 'Object 변수나 함수'가 저장된
kimdabang.tistory.com
DaoFactory의 userDao()를 여러 번 호출했을 때 동일한 오브젝트가 돌아올까에 대한 테스트 진행
직접 생성한 DaoFactory 오브젝트 출력 코드
DaoFactory factory = new DaoFactory();
UserDao dao1 = factory.userDao();
UserDao dao2 = factory.userDao();
System.out.println(dao1);
System.out.println(dao2);
/*
* 출력 결과
* springbook.dao.UserDao@118f375
* springbook.dao.UserDao@117a8bd
*/
출력 결과에서 알 수 있듯, 두 개는 각기 다른 값을 가진 동일하지 않은 오브젝트임
즉, 오브젝트 두 개가 생겼다는 사실을 알 수 있음
userDao를 매번 호출하면 계속 새로운 오브젝트가 만들어질 것이라는 것을 알 수 있음
스프링 컨텍스트로부터 가져온 오브젝트 출력 코드
ApplicationContext context = new AnnotationConfigApplication(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.dao.UserDao@ee22f7
* springbook.dao.UserDao@ee22f7
*/
두 오브젝트의 출력 값이 같으므로, getBean()을 두 번 호출해서 가져온 오브젝트가 동일하다는 사실을 알 수 있음
확실히 하려면 dao3 == dao4를 출력해보면 알 수 있음
true의 결과값이 출력됨
그렇다면 동작방식이 어떻게 다르길래 다른 결과가 출력되는 것일까?
1.6.1. 싱글톤 레지스트리로서의 애플리케이션 컨텍스트
애플리케이션 컨텍스트는 우리가 만들었던 오브젝트 팩토리와 비슷한 방식으로 동작하는 IoC 컨테이너
동시에 이 애플리케이션 컨텍스트는 싱글톤을 저장하고 관리하는 싱글톤 레지스트리(singleton registry)이기도 함
스프링은 기본적으로 별다른 설정을 하지 않으면 내부에서 생성하는 빈 오브젝트를 모두 싱글톤으로 만듦
여기서 싱글톤이라는 것은 디자인 패턴에서 나오는 싱글톤 패턴과 비슷한 개념이지만 그 구현 방법은 확연히 다름
서버 애플리케이션과 싱글톤
스프링이 주로 적용되는 대상이 자바 앤터프라이즈 기술을 사용하는 서버환경이기 때문에 스프링을 싱글톤 빈으로 만듦
매번 클라이언트에서 요청이 올 때마다 각 로직을 담당하는 오브젝트를 새로 만든다면 지나친 부하로 서버가 감당하기 힘들어짐
따라서 엔터프라이즈 분야에서는 서비스 오브젝트라는 개념 도입
서블릿
1. 의미 : 자바 엔터프라이즈 기술의 가장 기본이 되는 서비스 오브젝트
2. 특징 : 대부분 멀티스레드 환경에서 싱글톤으로 동작
-> 서블릿 클래스당 하나의 오브젝트만 만들어두고, 사용자의 요청을 담당하는 여러 스레드에서 하나의 오브젝트를 공유해 동시에 사용
애플리케이션 안에 제한된 수, 대개 한 개의 오브젝트만 만들어서 사용하는 것이 싱글톤 패턴의 원리
서버 환경에서는 싱글톤 사용 권장
하지만 디자인 패턴에 소개된 싱글톤 패턴은 사용하기가 까다롭고 여러 가지 문제점이 있음
심지어 피해야할 패턴이라는 의미로 안티패턴(anti pattern)이라고 부르는 사람도 있음
싱글톤 패턴(Singleton Pattern)
(가장 자주 활용되는 패턴이기도 하지만 가장 많은 비판을 받는 패턴이기도 함)
1. 의미 : 어떤 클래스를 애플리케이션 내에서 제한된 인스턴스 개수, 이름처럼 주로 하나만 존재하도록 강제하는 패턴
2. 특징
1) 하나만 만들어지는 클래스의 오브젝트는 애플리케이션 내에서 전역적으로 접근 가능
2) 단일 오브젝트만 존재해야 하고, 이를 애플리케이션의 여러 곳에서 공유하는 경우 주로 사용
싱글톤 패턴의 한계
자바에서 싱글톤 구현하는 방법
1. 클래스 밖에서는 오브젝트를 생성하지 못하도록 생성자를 private으로 만듦
2. 생성된 싱글톤 오브젝트를 저장할 수 있는 자신과 같은 타입의 static 필드 정의
3. 생성
3-1) 호출 시점에 생성
- static 팩토리 메소드인 getInstance()를 만들고 이 메소드가 최초로 호출되는 시점에서 한 번만 오브젝트가 생성되도록 함
- 생성된 오브젝트는 static 필드에 저장
3-2) static 필드의 초기값으로 오브젝트를 미리 만듦
4. 한 번 싱글톤 오브젝트가 만들어지고 난 후에는 getInstance() 메소드를 통해 이미 만들어져 static 필드에 저장해둔 오브젝트 넘겨줌
UserDao.java
public class UserDao {
private statuc UserDao INSTANCE;
...
private UserDao(ConnectionMaker connectionMaker) {
this.connectionMaker = connectionMaker;
}
public static syncronized UserDao getInstance() {
if (INSTANCE == null) INSTANCE = new UserDao(???);
return INSTANCE;
}
...
}
위 코드를 살펴보니 코드가 지져분해졌다는 느낌이 듦
문제점도 많이 보임
싱글톤 패턴 구현 방식의 문제점
1. private 생성자를 갖고 있기 때문에 상속 불가능
- 기술적인 서비스만 제공하는 경우라면 상관없겠지만, 애플리케이션의 로직을 담고 있는 일반 오브젝트의 경우 싱글톤으로 만들었을 때 객체지향적인 설계의 장점을 적용하기가 어려움
- 상속과 다형성 같은 객체지향의 특징이 적용되지 않는 static 필드와 메소드를 사용하는 것도 동일한 문제 발생시킴
2. 싱글톤은 테스트가 어렵거나 경우에 따라 테스트 자체가 불가능함
- 만들어지는 방식이 제한적이기 때문에 테스트에서 사용될 때 mock 오브젝트 등으로 대체하기 힘듦
- 싱글톤은 초기화 과정에서 생성자 등을 통해 사용할 오브젝트를 다이나믹하게 주입하기도 힘들기 때문에 필요한 오브젝트는 직접 오브젝트를 만들어 사용해야 함 > 테스트용 오브젝트로 대체 힘듦
3. 서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못함
- 서버에서 클래스 로더를 어떻게 구성하고 있느냐에 따라 싱글톤 클래스임에도 하나 이상의 오브젝트가 만들어질 수 있음
- 자바 언어를 이용한 싱글톤 패턴 기법은 서버환경에서는 싱글톤이 꼭 보장된다고 볼 수 없음
- 여러 개의 JVM에 분산돼서 설치가 되는 경우에도 각각 독립적으로 오브젝트가 생김
4. 싱글톤의 사용은 전역 상태를 만들 수 있기 때문에 바람직하지 못함
- 싱글톤은 사용하는 클라이언트가 정해져 있지 않음
- 싱글톤의 static 메소드를 이용해 언제든지 싱글톤에 쉽게 접근할 수 있기 때문에 애플리케이션 어디서든지 사용가능 > 자연스럽게 전역상태(global state)로 사용되기 쉬움
- 아무 객체나 자유롭게 접근하고 수정하고 공유할 수 있는 전역 상태를 갖는 것은 객체지향 프로그래밍에서는 권장되지 않는 프로그래밍 모델
싱글톤 레지스트리
싱글톤 레지스트리
1. 등장 배경
1) 스프링 : 서버환경에서 싱글톤이 만들어져서 서비스 오브젝트 방식으로 사용되는 것 적극 지지
2) 하지만 자바의 기본적인 싱글톤 패턴의 구현 방식 : 단점 다수 존재
=> 따라서 스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능 제공 > 이것이 싱글톤 레지스트리
2. 스프링 컨테이너와의 관계
- 스프링 컨테이너는 싱글톤을 생성, 관리, 공급하는 싱글톤 관리 컨테이너이기도 함
3. 특징
1) 평범한 자바 클래스를 싱글톤으로 활용하게 해줌(static 메소드와 private 생성자를 사용해야 하는 비정상적인 클래스가 아님)
- 장점(static 메소드와 private 생성자를 사용해야 하는 비정상적인 클래스와의 비교)
a) public 생성자 가능
b) 테스트를 위한 mock 오브젝트로 대체하는 것 간단
c) 생성자 파라미터를 이용해 사용할 오브젝트를 넣어주는 것 가능
2) 관리가 손쉬움 : 오브젝트 생성에 관한 모든 권한은 IoC 기능을 제공하는 애플리케이션 컨텍스트에게 있기 때문
- IoC방식의 컨테이너를 사용해서 생성과 관계설정, 사용 등에 대한 제어권을 컨테이너에게 넘김으로써 가능
3) 싱글톤 패턴과 달리 스프링이 지지하는 객체지향적인 설계 방식과 원칙, 디자인 패턴(싱글톤 패턴 제외) 등을 적용하는 데 아무런 제약이 없음
스프링은 IoC(Inversion Of Control) 컨테이너일 뿐만 아니라, 고전적인 싱글톤 패턴을 대신해 싱글톤을 만들고 관리해주는 싱글톤 레지스트리임
스프링이 빈을 싱글톤으로 만드는 것은 결국 오브젝트의 생성 방법을 제어하는 IoC 컨테이너로서의 역할
다음은 싱글톤으로 만들어지기 때문에 주의해야 할 점에 대해 알아보자
1.6.2. 싱글톤과 오브젝트의 상태
싱글톤은 멀티스레드 환경이라면 여러 스레드가 동시에 접근해서 사용 가능
따라서 상태관리에 주의해야 함
무상태(stateless) 방식
1. 의미 : 상태정보를 내부에 갖고 있지 않음
2. 사용하는 경우 : 싱글톤이 멀티스레드 환경에서 서비스 형태의 오브젝트로 사용되는 경우
3. 이유
- 다중 사용자의 요청을 한꺼번에 처리하는 스레드들이 동시에 싱글톤 오브젝트의 인스턴스 변수를 수정하는 것은 매우 위험 > 상태유지(stateful) 방식으로 만들지 않음
4. 예외 : 읽기 전용의 값
5. 무상태 방식으로 클래스를 만드는 방법
- 메소드 파라미터나 메소드 안에서 생성되는 로컬 변수 사용 : 매번 새로운 값을 저장할 독립적인 공간이 만들어지기 때문에 싱글톤이라고 해도 여러 스레드가 변수의 값을 덮어쓸 일은 없음
상태유지(stateful) 방식 : 인스턴스 필드의 값을 변경하고 유지
UserDao.java
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처럼 개별적으로 바뀌는 정보는 로컬 변수로 정의하거나, 파라미터로 주고받으면서 사용하게 해야 함
ConnectionMaker는 읽기전용 정보이기 때문에 인스턴스 변수로 정의해서 사용해도 상관없음
DaoFactory에 @Bean을 붙여 만들었기 때문에 스프링이 관리하는 빈이 될 것이고, 별다른 설정이 없다면 기본적으로 오브젝트 한 개만 만들어져서 UserDao의 connectionMaker 인스턴스 필드에 저장됨
단순한 읽기전용 값이라면 static final이나 final로 선언하는 것이 나음
1.6.3. 스프링 빈의 스코프
빈의 스코프(scope)
1. 정의 : 스프링이 관리하는 오브젝트, 즉 빈이 생성되고, 존재하고, 적용되는 범위
2. 종류(여기서는 간단하게 4가지만 소개 - 10장에서 자세히 알아볼 예정)
1) 싱글톤(singleton) 스코드
- 스프링 빈의 기본 스코프
- 스프링에서 만들어지는 대부분의 빈은 싱글톤 스코프를 가짐
- 컨테이너 내에 한 개의 오브젝트만 만들어져서 강제로 제거하지 ㅇ낳는 한 스프링 컨테이너가 존재하는 동안 계속 유지
2) 프로토타입(prototype) 스코프
- 컨테이너에 빈을 요청할 때마다 매번 새로운 오브젝트 만들어줌
3) 요청(request) 스코프
- 웹을 통해 새로운 HTTP 요청이 생길 때마다 생성
4) 세션(session) 스코프
- 웹의 세션과 스코프가 유사
'STUDY > Spring' 카테고리의 다른 글
[Spring] @EnableJpaAuditing (0) | 2024.02.22 |
---|---|
[Spring] 왜 Entity에 @NoArgsConstructor(access = AccessLevel.PROTECTED)를 사용할까? (0) | 2024.02.21 |
[스프링] 오브젝트와 의존관계 5 (0) | 2022.10.28 |
[스프링] 오브젝트와 의존관계 4 (0) | 2022.10.14 |
[스프링] 오브젝트와 의존관계 3 (0) | 2022.10.14 |