-
1장) 1.6 싱글톤 레지스트리와 오브젝트 스코프 ~ 1.7 의존관계 주입(IoC)Java & Spring/토비의 스프링 3.1 2021. 5. 21. 10:38반응형
1장 오브젝트와 의존관계
1.6 싱글톤 레지스트리와 오브젝트 스코프
스프링의 Application Context는 예시로 직접 만들었던 오브젝트 팩토리(DaoFactory)와 차이가 있다.
- 오브젝트의 동일성과 동등성으로 말하면 이해하기 쉽다.
- DaoFactory는 호출할 때마다 새로운 오브젝트를 만들어서 반환한다. (다른 객체 -> 동등성)
- 스프링의 Application Context에 DaoFactory를 설정정보로 등록(@Configuration)하고 getBean으로 빈을 호출하면 같은 주소값을 가진 오브젝트를 반환한다. (같은 객체 -> 동일성)
싱글톤 레지스트리로서 Application Context
- Application Context는 IoC 컨테이너이자 싱글톤을 저장하고 관리하는 싱글톤 레지스트리이기도 하다.
스프링이 싱글톤으로 빈을 만든 이유
- 자바 엔터프라이즈 기술을 사용하는 서버 환경 때문
- 스프링이 처음 설계됐을 때, 높은 성능이 요구되는 환경이었음
- 다양한 기능을 담당하는 오브젝트들이 참여하는 계층형 구조로 이뤄진 경우가 대부분
- 아무리 GC의 성능이 좋아졌어도, 매 요청마다 오브젝트를 새로 생성해주면 부하가 걸림
- 그래서 자바 엔터프라이즈 분야는 서비스 오브젝트라는 개념을 일찍부터 사용
- 서블릿이 가장 기본이 되는 서비스 오브젝트
- 멀티스레드 환경에서 싱글톤으로 동작
- 서블릿이 가장 기본이 되는 서비스 오브젝트
- 자바 엔터프라이즈 기술을 사용하는 서버 환경 때문
싱글톤 패턴의 한계
- 싱글톤 구현 방법
- 생성자를 private으로 만든다
- 자신과 같은 타입의 스태틱 필드를 정의한다
- getInstance를 만들어 최초 호출되는 시점에서 한번만 오브젝트를 생성하고, 저장해둔 오브젝트를 넘겨준다
- 한계
- private 생성자 떄문에 상속할 수 없음
- 객체지향의 장점인 상속과 다형성을 적용할 수 없음
- 테스트하기 힘들다
- 테스트용 오브젝트로 대체하기 힘들다. (테스트 만드는데 지장있다는 것은 큰 단점)
- 하나만 만들어지는 것을 보장하지 못함
- 자바 언어를 이용한 싱글톤 패턴은 서버 환경에서는 싱글톤이 꼭 보장되지 않음
- 여러 개의 JVM에 분산돼서 설치가 되는 경우에도 각각 독립적으로 오브젝트가 생김 ~> 싱글톤으로서 가치가 떨어짐
- 전역 상태를 만들 수 있기 때문에 바람직하지 못함
- 전역 상태로 사용되기 쉬운데, 아무 객체나 자유롭게 접근하고 수정하고 공유할 수 있는 전역 상태를 갖는 것은 객체지향 프로그래밍에서는 권장되지 않는 프로그래밍 모델
- private 생성자 떄문에 상속할 수 없음
- 싱글톤 구현 방법
싱글톤 레지스트리
- 자바의 기본 싱글톤 패턴 구현에 단점이 있어서 스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 싱글톤 레지스트리를 제공
- 생성과 관계설정, 사용 등에 대한 제어권을 컨테이너에게 넘겨 관리. (오브젝트 생성에 관한 권한이 IoC기능을 제공하는 Application Context에 있음)
- 테스트 환경에서 자유롭게 오브젝트를 만들거나 mock으로 대체 가능
싱글톤과 오브젝트의 상태
- 멀티스레드 환경에서는 동시 접근 가능성이 있기 때문에 상태 관리에 주의를 기울여야함
- 싱글톤은 상태정보를 내부에 갖고 있지 않은 무상태 방식으로 만들어져야한다.
- 개별적으로 바뀌는 정보는 로컬 변수로 정의하거나, 파라미터로 주고받으며 사용하게 해야함
- 단순한 읽기 전용 값은 static final or final로 선언하는 편이 좋다
- 멀티스레드 환경에서는 동시 접근 가능성이 있기 때문에 상태 관리에 주의를 기울여야함
스프링 빈의 스코프
- 빈 스코프
- 스프링이 관리하는 오브젝트(빈)가 생성, 존재, 적용되는 범위
- 싱글톤 스코프
- 컨테이너 내에 하나의 오브젝트만 만들어서 제거하지 않는 한 계속 존재
- 프로토타입 스코프
- 컨테이너에 빈을 요청할 때마다 매번 새로운 오브젝트를 생성해줌
- 요청 스코프
- HTTP 요청마다 생성
- 세션 스코프
- 웹의 세션과 스코프가 유사
- 최초의 요청을 발생시키고 브라우저를 닫을 때 까지 유지
- Bean을 정의할 때 session scope로 정의하면 브라우저가 서버에 최초의 요청을 보낼 때 Bean 객체가 주입
- 주입만 되는 것이지 session 영역에 저장되지는 않는다.
- 웹의 세션과 스코프가 유사
- 빈 스코프
1.7 의존관계 주입 IoC
제어의 역전(IoC)과 의존관계 주입
- IoC라는 용어가 매우 느슨하게 정의돼서 폭넓게 사용된다
~> 스프링을 IoC 컨테이너라고만 하면, 스프링이 제공하는 기능의 특징을 명확히 설명 못함 - 의존관계 주입(Dependency Injection)이라는 명칭이 더욱 명확해서 요즘은 DI로 많이 부른다.
- IoC 컨테이너라고 불리던 스프링을 DI 컨테이너라 부름
- IoC라는 용어가 매우 느슨하게 정의돼서 폭넓게 사용된다
런타임 의존 관계 설정
의존관계
- 항상 방향성을 부여해야함(의존하는 관계까 있어야함) (A가 B에 의존한다 ~> A->B)
- A가 B 메소드를 호출하여 사용하는 경우
- B가 변하면 A에 영향을 미친다는 의미
- 반대로 B는 A에 의존하지 않기 때문에, A가 변화해도 B에 영향 X
- 항상 방향성을 부여해야함(의존하는 관계까 있어야함) (A가 B에 의존한다 ~> A->B)
UserDao의 의존 관계
- UserDao -> ConnectionMaker(인터페이스)
- 위의 인터페이스가 변화하면 당연히 UserDao도 변화해야겠지만, 구현체가 변해도 영향이 없음
- 이런 느슨한 관계를 결합도가 낮다고 한다.
- SOLID 원칙 중 의존 역전 원칙과 관련
- 위에서 설계에서 보이는 의존 관계 외에 런타임 시에 오브젝트 사이에서 만들어지는 관계가 있다.
- 런타임(오브젝트) 의존관계
- 프로그램 Running 후, UserDao 오브젝트가 만들어지고 의존관계를 맺는 대상(실제 사용대상인 오브젝트)을 의존 오브젝트라고 함
- 런타임(오브젝트) 의존관계
- 의존관계 주입은 의존 오브젝트와 사용할 주체(보통 클라이언트)인 오브젝트를 런타임 시에 연결해주는 작업
- 충족해야할 3 조건
- 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않음
- 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제 3의 존재가 결정
- 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공(주입)해줌으로써 만들어짐
- 충족해야할 3 조건
- UserDao -> ConnectionMaker(인터페이스)
UserDao의 의존관계 주입
// 1 public UserDao() { connectionMaker = new DConnectionMaker(); } /////////////////////////////////////////////////////// //2 public UserDao(ConnectionMaker connectionMaker) { this.connectionMaker = connectionMaker(); }
1번의 경우 모델링 때의 의존관계를 UserDao과 결정하고 관리한다
- 런타임 시 의존관계가 코드 속에 미리 결정됨 + 필요없는 관심
2번의 경우 IoC 방식을 써서 제 3의 존재인 런타임 의존관계 결정 권한을 위임
- 따라서, DaoFactory는 DI컨테이너라고 부를 수 있음
- 이렇게 생성자(메소드)를 통해 DI 컨테이너가 UserDao에게 주입해주는 것과 같다고 해서 의존관계 주입이라고 부름
DI는 자신이 사용할 오브젝트에 대한 선택과 생성 제어권을 외부에 넘기고, 수동적으로 주입받은 오브젝트를 사용한다는 점에서 IoC 개념에 잘 맞는다.
- 스프링 컨테이너의 IoC는 주로 의존관계 주입 or DI 라는데 초점이 맞춰져있음
의존관계 검색과 주입
의존관계 검색(Dependency Lookup(DL))
외부로부터 주입이 아니라 스스로 검색을 사용
런타임 시 의존관계를 맺을 오브젝트를 결정 + 오브젝트의 생성 작업은 외부 컨테이너에게 IoC로 맡김
가져올 때는 메소드 주입이 아닌 스스로 컨테이너에게 요청하는 방법을 사용
public UserDao() { DaoFactory daoFactory = new DaoFactory(); this.connectionMaker = daoFactory.connectionMaker(); }
IoC 개념을 잘 따르며 혜택을 받지만, 스스로 IoC 컨테이너인 DaoFactory에게 요청하는 것
- 스프링의 IoC 컨테이너인 Application Context는 getBean 메소드를 통해 의존관계 검색을 제공
의존관계 주입 vs 의존관계 검색
의존관계 검색은 성격이 다른 오브젝트에 의존하게 되는 것이므로 바람직하지는 않음
~> 대부분은 의존관계 주입(DI)을 사용
언제 필요한 것인가?
- 스프링 컨테이너에 담긴 오브젝트를 사용하려면 한 번은 의존관계 검색 방식을 사용해서 오브젝트를 가져와야함
의존관계 검색은 검색하는 오브젝트가 자신이 스프링 빈일 필요가 없다
의존관계 주입은 DI가 적용되려면 반드시 컨테이너가 만드는 빈 오브젝트여야함
- DI를 원하는 오브젝트는 먼저 자기 자신이 컨테이너가 관리하는 빈이 되어야함
의존관계 주입의 응용
- 기능 구현의 교환
- DI 방식을 사용하지 않고 로컬 DB 연결로 개발하다가 배포하는 상황을 떠올려보자...
- 변경할 코드가 수백 수천 줄이 될거다...
- DI 방식을 적용하면 배포 시점에도 DAO 클래스를 수정할 필요가 없어진다.
- connection 부분이 real server, beta server, alpha server, local server에 따라 바뀌겠지만 한줄이다
- 근데 이마저도 설정파일로 해결 가능하다(해당 부분은 나중 챕터에서 나올 것 같음)
- connection 부분이 real server, beta server, alpha server, local server에 따라 바뀌겠지만 한줄이다
- DI 방식을 사용하지 않고 로컬 DB 연결로 개발하다가 배포하는 상황을 떠올려보자...
- 부가기능 추가
- 만약 DB 연결횟수를 카운팅한다면? DI로 충분
- UserDao로 예를 들어보자
- 기존 UserDao -> DConnectionMaker의 의존 관계가 있음
- DConnectionMaker는 DB 연결 정보를 담당
- DB연결마다 카운트 기능을 포함한 CountingConnectionMaker 관계를 추가하고 ConnectionMaker의 코드를 살짝 수정하면
- UserDao -> CountingConnectionMaker -> DConnectionMaker 로 의존관계가 바뀐다. (이 때, CountingDaoFactory를 만들어서 DConnectionMaker와 관계 설정)
- DB 연결 시마다 CountingConnetionMaker가 열심히 카운팅 할 것
- 기존 UserDao -> DConnectionMaker의 의존 관계가 있음
- 부가기능을 추가해서 런타임 의존관계를 새롭게 맺어주는데 용의하다
- 기능 구현의 교환
메소드를 이용한 의존관계 주입
- 수정자 메소드 주입
- setter
- 외부에서 오브젝트 내부의 Attribute 값을 변경하려는 용도
- 일반 메소드 주입
- 수정자 메소드처럼 set으로 시작해야하고, 한 번에 한 개의 파라미터만 가지는게 싫다!
여러 파라미터를 갖는 일반 메소드를 DI 용으로 사용하자
는 의미로 사용- 모든 파라미터를 받는 수정자를 만들 수 있지만 실수할 수 있기 때문에, 적절한 파라미터 단위로 끊어서 사용
- 근데, 요즘에는 이런 경우 Builder를 사용한다
- 전통적으로 DI 중 가장 많이 사용
- 네이밍이 아주 중요 (특별한 이름이 아니라면 setConnectionMaker와 같이 set을 붙이자)
- 의존관계를 주입하는 시점과 방법이 달라졌을 뿐, 결과는 동일
- 수정자 메소드 주입
용어 정리
의존관계 주입, 의존성 주입, 의존 오브젝트 주입
- Dependency Injection ~> 의존성 주입, 의존(종속) 오브젝트 주입
- 오브젝트의 레퍼런스가 전달될 뿐이다.
- 오브젝트 레퍼런스를 외부로부터 제공(주입)받고 이를 통해 다른 오브젝트와 다이나믹하게 의존관계가 만들어지는 것이 DI의 핵심
- 장점
- 코드에는 런타임 클래스에 대한 의존관계가 나타나지 않음
- 인터페이스를 통해 결합도가 낮은 코드 ~> 다른 책임을 가진 사용 의존 관계에 있는 대상이 변경되어도 영향X
- 변경을 통한 다양한 확장방법에 자유롭다
DI 받는다
- 주입해준다고 다 DI가 아니다.
- 다이나믹하게 구현 클래스를 결정해서 제공받도록 Interface 타입의 파라미터를 통해 이뤄줘야함(메소드)
반응형'Java & Spring > 토비의 스프링 3.1' 카테고리의 다른 글
2장) 2.3 개발자를 위한 테스팅 프레임워크 JUnit (0) 2021.06.01 2장) 2.1 USERDAOTEST 다시보기 ~ 2.2 USERDAOTEST 개선 (0) 2021.05.27 1장) 1.8 XML을 이용한 설정 ~ 1.9 정리 (0) 2021.05.27 1장) 1.3 DAO의 확장 ~ 1.5 스프링의 IoC (1) 2021.05.17 1장 오브젝트와 의존관계 (1.1 초난감 DAO ~ 1.2 DAO의 분리) (1) 2021.05.13