토비의 스프링 3.1 vol.1 1장 - 오브젝트와 의존관계

1장에선 JDBC를 사용하는 UserDao1클래스를 구현하고 관심사의 분리SoC:Separation of Concerns2와 디자인 패턴을 적용하면서 리펙토링해 나가는 방식을 통해 스프링 프레임 워크의 개념과 필요성에 대해 설명한다. 스프링에서 제공해 주는 IoCInverse of Control3를 직접 구현해보며 프레임워크의 필요성을 느낄 수 있었다.

템플릿 메소드 패턴 (Template Method Pattern) : 슈퍼클래스 또는 인터페이스에 기본적인 로직의 흐름을 만들고, 그 기능의 일부를 오버라이딩이 가능한 메소드로 만든 뒤 서브 클래스에서 필요에 맞에 구현해서 사용하는 디자인 패턴

  • 단점
    • 자바 클래스는 다중 상속을 지원하지 않는다. (서브 클래스가 이미 다른 상속을 하고 있을 경우 템플릿 클래스에 대한 상속을 하기 힘들게 된다.)
    • 슈퍼 클래스 내부의 변경이 있을때, 이를 확장하는 서브 클래스도 함께 수정해야할 수 있다. (이 때문에 슈퍼클래스의 변경이 없도록 제약을 가해야할 수도 있다.)
    • 오버라이딩 되는 메서드가 서브 클래스의 갯수만큼 반복된다. (자주 변경이 되는 메서드를 가정하고 오버라이딩 가능하게 한 것이지만, 서브 클래스들에서 비슷한 작업을 수행할 경우 다시 중복된 코드가 생성된다.)

팩토리 메소드 패턴 (Factory Method Pattern) : 서브 클래스에서 구체적인 오브젝트 생성 방법을 결정하는 디자인 패턴.

전략 패턴(Strategy Pattern) : 어떤 객체(Context)의 기능에서 필요에 따라 변경이 필요한 알고리즘(Strategy)을 인터페이스를 통해 통째로 외부로 분리시키고, 이를 구현한 구체적인 알고리즘 클래스를 필요에 따라 바꿔 사용할 수 있게 하는 디자인 패턴.

  • Context : 기능을 수행하는 대상.
  • Strategy : Context에서 수행할 기능의 과정 중 변경될 수 있는 알고리즘.
  • Client : Context를 사용하는 개체, Client는 Context가 사용할 Strategy를 생성자 등을 통해 Context에 제공한다.

스프링의 IoC

스프링에서 IoC를 통해 제어하는 오브젝트인 빈Bean은 어플리케이션 컨텍스트Application Context에 요청하면 빈 팩토리Bean Factory에서 새로운 빈을 요청마다 생성하지 않는다. 별다른 설정을 하지 않는다면 빈은 싱글톤으로 만들어져 어플리케이션 컨텍스트에 의해 저장 및 관리된다.

스프링 용어 정리

  • 빈(Bean) : 스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트.
  • 빈 팩토리(Bean Factory) : 빈의 등록, 생성, 조회 그리고 관계설정같은 제어를 담당하는 IoC 컨테이너. 보통 직접 사용되지 않고 빈 팩토리를 확장한 어플리케이션 컨텍스트를 이용한다.
  • 어플리케이션 컨텍스트(Application Context) : 빈 팩토리를 확장한 IoC 컨테이너. 빈 팩토리의 기본적인 기능과 스프링이 제공하는 각종 부가 서비스를 추가로 제공한다.
  • 설정정보/설정 메타정보(Configuration Metadata) : 어플리케이션 컨텍스트 또는 빈 팩토리가 IoC를 적용하기 위해 사용하는 메타정보.
  • 컨테이너/IoC 컨테이너(Container) : IoC 방식으로 빈을 관리한다는 의미로 어플리케이션 컨텍스트나 빈 팩토리를 컨테이너 또는 IoC 컨테이너로 지칭하기도 한다.

spring_application_context_flow

스프링에서 빈을 싱글톤으로 관리하는 이유는 스프링의 적용 대상이 대부분 자바 엔터프라이즈 기술을 사용하는 서버환경이기 때문이다. 많은 수의 클라이언트가 끊임없이 요청을 하는 상황에서 매번 새로운 빈이 생성되는 방식으로 작동한다면 오브젝트 생성과 가비지 컬렉션GC:Garbage Collection에 부하가 걸리게 되고 서버가 감당하기 힘들어지게 된다. 어플리케이션 안에서 한정된 수의 인스턴스를 생성하여 사용하는 싱글톤을 사용한다면 이러한 부하를 피할 수 있다.

일반적인 싱글톤을 구현하는 방법은 다음과 같다.

  • 클래스 밖에서는 오브젝트를 생성하지 못하도록 생성자를 private으로 만든다.
  • 생성된 싱글톤 오브젝트를 저장할 수 있는 자신과 같은 타입의 스태틱 필드를 정의한다.
  • 스태틱 팩토리 메소드인 getInstance()를 만들어 이 메소드가 처음 호출 되는 시점에 한번만 오브젝트를 만들어 static 필드에 저장히도록한다.
  • 오브젝트가 만들어진 후 getInstance() 메서드가 호출되면 static 필드에 저장된 오브젝트를 넘겨준다.

하지만 이러한 일반적인 싱글톤에는 다음과 같은 단점이 존재한다.

  • private 생성자를 갖기 때문에 상속할 수 없다.
  • 오브젝트 생성이 감춰져있어 Mock 오브젝트로 대체하거나 생성자를 통해 다이나믹하게 주입하기 힘들기 때문에 테스트하기 힘들다.
  • 서버가 분산되어 설치된 경우 독립적으로 싱글톤을 만들기 때문에 서버환경에서 싱글톤이 하나라는 것을 보장하지 못한다.
  • 어플리케이션 어디서든 getIntance() 메서드를 통해 싱글톤을 전역 상태(Global State)로 만들 수 있기 때문에 객체지향에서 권장되지 않는다.

스프링은 싱글톤 레지스트리Singleton Registry를 통해 직접 싱글톤을 만들고 관리하는 기능을 제공한다. 일반적인 싱글톤 구현방법과 달리, 스프링에선 일반적인 자바 클래스를 싱글톤으로 활용할 수 있다. 때문에 public 생성자를 가질 수 있고, 테스트 환경에서 자유롭게 빈 오브젝트를 만들 수 있다. 덕분에 스프링 빈 오브젝트는 스프링이 지지하는 객체지향적인 설계방식과 원칙, 디자인 패턴 등을 적용하는 데 아무런 제약이 없다.

이러한 스프링 빈을 구현할 때 주의해야할 것이 있는데, 싱글톤이 멀티스레드 환경에서 서비스 형태의 오브젝트로 사용되는 경우에는 상태정보를 내부에 갖고 있지 않은 무상태Stateless 방식으로 만들어져야한다는 것이다. 다중 사용자의 요청을 한번에 처리하는 스레드들이 싱글톤의 인스턴스 변수를 수정한다면 데이터가 엉망이 될 수 있다. 때문에 무상태 방식으로 클래스를 만드는 경우 각 요청에대한 정보나, DB 그리고 서버 리소스로 부터 생성한 정보는 메소드의 파라메터와 로컬변수 그리고 리턴 값 등을 이용해야한다.

스프링에선 빈을 기본적으로 싱글톤으로 만들어 관리하지만, 빈의 스코프Scope 설정을 통해 싱글톤 이외의 스코프를 가질 수 있다. 프로토타입Prototype은 컨테이너에 요청할 때 마다 새로운 오브젝트를 만들어준다. 그 외에도 HTTP 요청이 생길때 오브젝트를 생성하는 요청 스코프Request, 웹 세션과 스코프가 유사한 세션 스코프Session 등 싱글톤 외에도 다양한 스코프가 존재한다.

객체지향 설계 원칙(SOLID) : 로버트 마틴이 정리한 객체지향 설계 원칙

  • 단일 책임 원칙(SRP:Single Responsibility Principal)
  • 개방 폐쇄 원칙(OCP:Open-Closed Principal) : 클래스나 모듈은 확장에는 열려있어야하고 변경에는 닫혀있어야 한다.
  • 리스코프 치환 원칙(LSP:Liskov Subsitution Principal)
  • 인터페이스 분리 원칙(ISP:Interface Segregation Principal)
  • 의존관계 역전 원칙(DIP:Dependency Inversion Principal)

의존관계 주입(DI:Depedency Injection)

IoC라는 폭넓게 정의되어 사용하는 용어로는 스프링의 특징을 설명하기 힘들어 의존관계 주입이라는 이름을 만들어 사용하기 시작했다. 의존관계가 있다는 말은 A가 B에 의존할 때, 의존대상인 B가 변하면 A에 영향을 미친다는 뜻이다. 때문에 의존관계에는 방향성이 존재한다.

설계 모델의 관점이나 클래스와 인터페이스 사이의 의존관계가 아닌 런타임 시에 오브젝트 사이의 의존관계를 런타임 의존관계라고 한다. 의존관계의 주입은 이러한 런타임 시에 사용대상인 의존 오브젝트와 그것을 사용할 주체인 클라이언트를 연결해 주는 작업을 말한다. 정리하자면, 의존관계 주입은 컨테이너나 팩토리 같은 제3의 존재가, 인터페이스에만 의존하는 클라이언트에 구체적인 의존 오브젝트의 레퍼런스를 제공하는 것을 말한다.

스프링이 제공하는 IoC에는 의존관계 주입만 있는것이 아니다. 의존관계를 외부로 부터 주입 받는 것이 아닌 스스로 getBean()과 같은 메소드를 통해 빈을 검색하여 의존관계를 형성하는 의존관계 검색DL:Dependency Lookup도 있다. 의존관계 검색 또한 의존관계 주입의 거의 모든 장점을 갖지만, 클라이언트 안에 오브젝트 팩토리 클래스나 스프링 API가 나타나기 때문에 깔끔한 코드를 기대하기 어려워 대개는 의존관계 주입방식을 사용하는 편이 낫다.

하지만 클라이언트와 의존 오브젝트가 모두 빈으로 관리되어야하는 의존관계 주입과 달리 의존관계 검색은 클라이언트가 빈으로 관리되지 않아도 된다는 특징이 있다.

  1. DAO : Data Access Object의 약자. 사용자 정보를 DB와 연동해 관리하도록 하는 클래스. DB 에서 로우로 관리되는 데이터들을 보통 어플리케이션에서 인스턴스로 변환하여 사용한다. Dao 클래스는 이런 변환과 DB와의 연결을 담당한다. 

  2. Soc : 관심이 같은 것 끼리는 하나의 객체 안으로 또는 친한 객체로 모이게하고, 관심이 다른 것은 가능한 따로 떨어져서 서로 영향을 주지 않도록 분리하는 것. 관심에 효과적으로 집중할 수 있게 만들 수 있다. 

  3. IoC : 프로그램의 제어 흐름 구조가 뒤바뀌는 것. 제어의 역전에서 오브젝트는 자신이 사용할 오브젝트를 스스로 선택하지 않고, 위임받은 제어 권한을 갖는 특별한 오브젝트에 의해 결정되고 만들어진다.