티스토리 뷰

오브젝트

스프링의 핵심 철학은 다음과 같다. J2EE 시절의 혼란 속에서 잃어버린 객체지향 기술의 진정한 가치를 회복시키고, 그로부터 OOP가 제공하는 폭넓은 혜택을 누릴 수 있도록 기본으로 돌아가자는 것이다.

따라서 스프링이 가장 관심을 많이 두는 대상은 오브젝트이다. 오브젝트에 대한 관심은 오브젝트의 기술적인 특징과 사용 방법을 넘어서 오브젝트의 설계로 발전하게 된다.

관심사의 분리

자바빈(JavaBean)

자바빈은 다음 두 가지 관례를 따라 만들어진 오브젝트를 가리킨다.

  • 디폴트 생성자
    • 자바빈은 파라미터가 없는 기본 생성자를 갖고 있어야 한다. 프레임워크 등에서 Reflection을 이용해 오브젝트를 생성하기 때문에 필요하다.
  • 프로퍼티
    • 자바빈이 노출하는 이름을 가진 속성을 프로퍼티라고 한다. 프로퍼티는 수정자 메소드인 setter로 수정하고, 접근자 메소드인 getter로 조회한다.

객체지향의 세계에서는 모든 것이 변한다. 오브젝트에 대한 설계와 이를 구현한 코드가 변한다는 뜻이다. 그래서 개발자가 객체를 설계할 때 가장 염두에 두어야 할 사항은 바로 미래의 변화를 어떻게 대비할 것인가이다.
이에 대한 고민은 분리확장을 고려한 설계로 이어진다.

보통의 문제는 다음과 같다. 변화는 대체로 집중된 한 가지 관심에 대해 일어나지만, 그에 따른 작업은 한 곳에 집중되지 않는 경우가 많은 것이다. 우리가 준비해야 할 일은 한 가지 관심이 한 군데에 집중되게 해야 하는 것이다. 이것이 바로 관심사의 분리다.

추상클래스를 활용한 디자인 패턴

추상클래스의 상속을 통해 변경 가능성을 확장할 수 있다.

  • 템플릿 메소드 패턴
    • 슈퍼클래스에 기본 로직을 만들고, 변화하는 부분을 추상 메소드(혹은 overriding 가능한 메소드)로 만든 뒤 상속 받는 서브클래스에서 필요에 맞게 구현하는 방법
  • 팩토리 메소드 패턴
    • 서브클래스에서 구체적인 오브젝트 생성 방법과 클래스를 결정하게 하고 슈퍼클래스에서 해당 오브젝트를 사용하는 방법

추상클래스를 만들고 이를 상속한 서브클래스에서 변화가 필요한 부분을 바꿔서 쓸 수 있게 만든 이유는 바로 이렇게 변화의 성격이 다른 것을 분리해서, 서로 영향을 주지 않은 채로 각각 필요한 시점에 독립적으로 변경할 수 있게 하기 위해서다.

위 디자인 패턴들은 상속을 사용했다는 단점이 있다. 자바에서 다중상속은 불가능하기 때문에 또다른 상속을 통한 확장이 불가능하고, 슈퍼클래스와 서브클래스의 관계가 너무 밀접하기 때문에, 추후에 슈퍼클래스를 수정할 때 서브클래스까지 같이 수정해야 할 가능성이 크다.

인터페이스와 런타임 오브젝트 관계

상속 대신에, 클래스를 아예 분리하는 방향으로 리팩토링 해보자.

인터페이스를 사용하는 이유

한 클래스에서 다른 클래스에 직접적인 구현체 대신 인터페이스를 넣어주는 방법을 사용하면, 인터페이스는 클래스들이 서로 긴밀하게 연결되어 있지 않도록, 주입하는 클래스를 한 단계 추상화시켜서 중간에 추상적인 느슨한 연결고리를 만들어준다. 추상화란 어떤 것들의 공통적인 성격을 뽑아내어 이를 따로 분리해내는 작업을 말한다.

인터페이스를 사용하면 실제 구현클래스를 바꿔도 신경 쓸 일이 없다. 구현클래스를 정하는 일은 이제부터 당사자가 아닌 제 3의 클라이언트에게 맡기자.

클라이언트의 책임은 어떤 두 클래스를 런타임 오브젝트 관계를 갖는 구조로 만들어주는 것이다.

클래스 사이의 관계

한 클래스 코드 내에 다른 클래스 이름이 직접적으로 나타난다.

오브젝트 사이의 관계

코드에서는 특정 클래스를 전혀 알지 못하더라도 해당 클래스가 구현한 인터페이스를 사용했다면, 그 클래스 오브젝트를 사용할 수 있다.
모델링 시점에서는 보이지 않고 존재하지 않았던 오브젝트 관계가, 런타임 시에 동적으로 맺어지면서 나타난다.

OCP와 전략 패턴

  • 개방 폐쇄 원칙(OCP, Open-Closed Principle)
    • 클래스나 모듈은 확장에는 열려 있어야 하고 변경에는 닫혀 있어야 한다. 즉, 인터페이스를 통해 확장은 열어놓고, 그 인터페이스를 이용하는 부분은 닫아놓는다.
  • 높은 응집도, 낮은 결합도
    • 응집도가 높다 : 변화가 일어날 때 해당 모듈에서 변하는 부분이 크다.
    • 결합도가 낮다 : 하나의 오브젝트가 변경이 일어날 때 관계를 맺고 있는 다른 오브젝트에게 변화를 요구하는 정도가 낮다.
  • 전략 패턴(Strategy Pattern)
    • 자신의 기능 맥락(Context)에서, 필요에 따라 변경이 필요한 알고리즘을 인터페이스를 통해 외부로 분리시키고, 그 구현 클래스를 바꿔서 사용할 수 있게 하는 디자인 패턴

스프링의 IoC

애플리케이션 컨텍스트

스프링의 IoC에 대해 알아보자. 스프링의 핵심은 빈 팩토리 또는 애플리케이션 컨텍스트라고 불리는 것이다.

  • 빈(Bean)
    • 스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트. Java 원두의 커피 콩이다! 맛있겠다

빈의 생성과 관계설정 같은 제어를 담당하는 IoC 오브젝트를 빈 팩토리 또는 애플리케이션 컨텍스트라고 한다.

보통 외부에서 설정 정보를 가지고 와 사용하는데, 이 설정 정보와 애플리케이션 컨텍스트가 애플리케이션을 만들기 위한 설계도 같은 느낌이다.
설정 담당 클래스에 @Configuration, 대상 오브젝트 혹은 오브젝트 생성 메소드에는 @Bean을 붙인다.
등록한 빈을 사용하기 위해서는 ApplicationContext를 사용한다.

  • 직접 만든 구현체를 오브젝트 팩토리로 사용했을 때보다 ApplicationContext를 사용했을 때 얻을 수 있는 장점
    • 클라이언트는 구체적인 팩토리 클래스를 알 필요 없이, ApplicationContext에서 필요한 빈 오브젝트를 가져올 수 있다.
    • ApplicationContext는 단지 오브젝트 생성, 관계설정만 해주는 것이 아니라, 생성 방식, 시점, 전략, 자동생성, 오브젝트 후처리, 정보의 조합 등 여러 다양한 기능을 제공해 줄 수 있다.
    • 빈을 검색하는 다양한 방법을 제공한다.

싱글톤 레지스트리

스프링은 여러 번에 걸쳐서 빈을 요청하더라도 매번 동일한 오브젝트를 돌려준다. ApplicationContext는 싱글톤을 저장하고 관리하는 싱글톤 레지스트리이다. 일반적인 자바의 싱글톤과는 구현 방법이 다르다.

보통의 Java 싱글톤 구현방법

  • 외부 접근을 막기 위해 생성자를 private으로 만든다.
  • 생성된 싱글톤 오브젝트를 저장하기 위해 자신과 같은 타입의 static field를 정의한다.
  • static factory method인 getInstance()를 통해 최초 호출 시에만 오브젝트를 만들고, 그 이후부터는 이미 만들어진 오브젝트를 돌려준다.

Java 싱글톤 구현방법의 문제점

  • private 생성자를 갖고 있기 때문에 상속할 수 없다. 따라서 다형성도 적용할 수 없다.
  • 만들어지는 방식이 제한적이기 때문에 목 오브젝트 등으로 대체하기가 힘들어, 테스트하기가 힘들다.
  • 서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다.
  • 싱글톤의 사용은 static, 전역 상태를 만들 수 있기 때문에 객체지향 관점에서는 바람직하지 못하다.

싱글톤 레지스트리

  • 평범한 자바 클래스도 싱글톤으로 관리해준다.
  • 객체지향적인 설계 방식과 원칙, 디자인 패턴 등을 적용하는 데 아무런 제약이 없다.
  • 멀티스레드 환경에서 싱글톤이 사용되는 경우에는 내부 상태정보가 없는 Stateless 방식으로 만들어져야 한다. 읽기전용은 괜찮겠지만.
    따라서 스프링의 싱글톤 빈으로 사용되는 클래스를 만들 때는, 개별적으로 바뀌는 정보는 로컬 변수로 정의하거나, 파라미터로 주고 받으면서 사용하게 해야 한다.

스프링 빈의 스코프(Scope)

빈이 생성되고, 존재하고, 적용되는 범위이다. 스프링 컨테이너가 존재하는 동안 계속 유지된다.

DI(Dependency Injection)

스프링이 제공하는 IoC 방식은 의존관계 주입(Dependency Injection)이다.

의존한다?

A가 B에 의존한다는 것은 B가 변하면 그것이 A에 영향을 미친다는 것이다. 의존관계에는 방향성이 있다.

구체적인 클래스끼리 의존관계를 맺는 것 보다 인터페이스에 대해서만 의존관계를 만들어두면, 결과적으로 인터페이스 구현 클래스와의 관계는 느슨해진다. 결합도가 낮다고 설명할 수 있다.
인터페이스를 통해 의존관계를 제한해주면 그만큼 변경에서 자유로워진다.

의존관계 주입

  • 클래스 모델이나 코드에는 런타임 시점의 의존관계가 드러나지 않는다. 따라서 인터페이스에만 의존하고 있어야 한다.
  • 런타임 시점의 의존관계는 컨테이너나 팩토리 같은 제 3의 존재가 결정한다.
  • 의존관계는 사용할 오브젝트에 대한 레퍼런스를 외부에서 제공(주입)해줌으로써 만들어진다.

의존관계 검색

  • 의존관계를 맺는 방법 중 ApplicationContext 등을 직접 불러와 검색을 요청하는 경우이다.
  • 의존관계 주입과 다른 점은, 검색을 하는 주체는 컨테이너가 관리하는 빈 오브젝트일 필요가 없다는 것이다.
  • 반대로 의존관계 주입은 두 오브젝트 모두 빈이어야 한다.

DI 시 부가기능 추가

  • 같은 인터페이스를 받는 구현체라면 어떠한 것이든지 상관없으니, 원래의 관계 사이에 부가기능을 담은 구현체를 추가할 수도 있다!

참고

토비의 스프링 3.1 Vol. 1 스프링의 이해와 원리

최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday