토비의 스프링 - 1장. 오브젝트와 의존관계(3)

 Date: 2022-02-15

1.4 제어의 역전(IoC)
1.5 스프링의 IoC
1.6 싱글톤 레지스트리와 오브젝트 스코프

1.4 제어의 역전(IoC)

  • 이전 장 에서 UserDaoConnectionMaker 인터페이스를 구현한 특정 클래스로부터 완벽하게 독립할 수 있도록 UserDao의 클라이언트(=UserDaoTest)에게 그 책임을 넘기도록 수정했다.
  • 하지만, UserDaoTest에서도 테스트 이외의 역할을 수행하므로, 성격이 다른 책임이나 관심사를 분리해야 한다.
  • 이번 장에서는 분리시킬 기능의 오브젝트를 생성하고, 생성된 오브젝트가 연결되도록 관계를 맺어주는 방법에 대해 알아보자.

오브젝트 팩토리

  • 분리시킬 기능을 담당하는 클래스를 만든다. 이 클래스의 역할은 객체의 생성 방법을 결정하고 그렇게 만들어진 오브젝트를 돌려주는 것인데, 이런 일을 하는 오브젝트를 흔히 팩토리(factory)라고 부른다.
  • 예제 코드 - UserDao의 생성 책임을 맡은 팩토리 클래스

    기존의 UserDaoTest 에서는 UserDao를 생성하기 위해 connectionMaker 구현체를 넘겨주어야 했다.
    new UserDao(connectionMaker)
    변경된 UserDaoTest 에서는 UserDaoFactory에서 UserDao의 생성을 담당한다.
    new UserDaoFactory().userDao()

  • 설계도로서의 팩토리 클래스
    • UserDaoFactory 클래스는 오브젝트들을 구성하고 그 관계를 정의하는 책임을 맡고 있는 설계도와 같은 역할을 한다.

    • UserDaoFactory를 분리했을 때 얻을 수 있는 장점은 매우 다양하지만, 그중에서 애플리케이션의 컴포넌트 역할을 하는 오브젝트와 애플리케이션의 구조를 결정하는 오브젝트를 분리했다는 데 가장 의미가 있다.

오브젝트 팩토리의 활용

제어권의 이전을 통한 제어관계 역전

  • 제어의 역전이란, 간단히 프로그램의 제어 흐름 구조가 뒤바뀌는 것이다.

    초기에 개발한 UserDao를 보면 테스트용 main() 메소드에서 UserDao를 직접 생성하고 사용한다. UserDao 또한 자신이 사용할 ConnectionMaker의 구현 클래스를 자신이 결정하여, 언제 어떻게 만들지를 스스로 관장한다.

  • 제어의 역전에서는 오브젝트가 자신이 사용할 오브젝트를 스스로 선택하지 않는다. 당연히 생성하지도 않는다. 자신이 어떻게 만들어지고 어디서 사용되는지를 알 수 없다.
    • 서블릿이나 JSP, EJB처럼 컨테이너 안에서 동작하는 구조는 간단한 방식이긴 하지만 제어의 역전 개념이 적용되어 있다고 볼 수 있다.
    • 제어의 역전 개념이 적용된 예를 디자인패턴에서도 여럿 찾아볼 수 있다.

      템플릿 메소드는 제어권을 상위 템플릿 메소드에게 넘기고 자신은 필요할 때 호출되어 사용되록 한다는, 제어의 역전이라는 개념을 활용해 문제를 해결하는 디자인 패턴이라고 볼 수 있다.

    • 프레임워크도 제어의 역전 개념이 적용된 대표적인 기술이다. (라이브러리 VS. 프레임워크)
  • 제어의 역전에서는 프레임워크 또는 컨테이너와 같이 애플리케이션 컴포넌트의 생성과 관계설정, 사용, 생명주기 관리 등을 관장하는 존재가 필요하다.

    원래 ConnectionMaker의 구현 클래스를 결정하고 오브젝트를 만드는 제어권은 UserDao에게 있었다. 그런데 지금은 DaoFactory에게 있다. DaoFactory는 오브젝트 수준의 가장 단순한 IoC 컨테이너(혹은 IoC 프레임워크)라고 불릴 수 있다.

1.5 스프링의 IoC

  • 스프링은 IoC를 모든 기능의 기초가 되는 기반 기술로 삼고 있으며, IoC를 극한까지 적용하고 있는 프레임워크다.
  • 스프링의 핵심을 담당하는 것은 빈 팩토리(=애플리케이션 컨텍스트)이다.

오브젝트 팩토리를 이용한 스프링 IoC

  • 빈(Bean) : 스프링이 제어권을 가지고 직접 만들고 관계를 부여하는 오브젝트 제어의 역전이 적용된 오브젝트
  • 빈 팩토리 : 스프링에서 빈의 생성과 관계설정 같은 제어를 담당하는 IoC 오브젝트
  • 애플리케이션 컨텍스트 : 별도의 정보를 참고해서 빈의 생성, 관계설정 등의 제어 작업을 총괄하는 빈 팩토리

✔︎ 빈 팩토리와 애플리케이션 컨텍스트라는 용어는 동일하다고 생각하면 된다.
빈 팩토리라고 말할 때는 빈을 생성하고 관계를 설정하는 IoC의 기본 기능에 초점을 맞춘 것이고, 애플리케이션 컨텍스트라고 말할 때는 애플리케이션 전반에 걸쳐 모든 구성요소의 제어 작업을 담당하는 IoC 엔진이라는 의미가 좀 더 부각된다고 보면 된다.

  • DaoFactory를 설정정보로 사용하는 애플리케이션 컨텍스트
    • 예제 코드 - 스프링 빈 팩토리가 사용할 설정정보를 담은 DaoFactory 클래스

      @Configuration
      public class DaoFactory {
          @Bean
          public UserDao userDao() {
              UserDao dao = new UserDao(connectionMaker());
              return dao;
          }
          
          @Bean
          public ConnectionMaker connectionMaker() {
              ConnectionMaker connectionMaker = new DConnectionMaker();
              return connectionMaker;
          }
      }
      

      애노테이션 방식 설정정보 (자바 코드의 탈을 쓰고 있지만, 사실은 XML과 같은 스프링 전용 설정정보라고 보는 것이 좋다.)

    • @Configuration : 스프링이 빈 팩토리를 위한 오브젝트 설정을 담당하는 클래스라고 인식
    • @Bean : 오브젝트 생성을 담당하는 IoC용 메소드라는 표시

애플리케이션 컨텍스트의 동작방식

  • 애플리케이션 컨텍스트는 ApplicationContext 인터페이스를 구현하는데, ApplicationContext는 빈 팩토리가 구현하는 BeanFactory 인터페이스를 상속했으므로 애플리케이션 컨텍스트는 빈 팩토리인 셈이다.

  • 애플리케이션 컨텍스트를 사용했을 때 얻을 수 있는 장점
    • 클라이언트는 구체적인 팩토리 클래스를 알 필요가 없다.
    • 오브젝트의 생성과 관계설정 뿐만 아니라 종합 IoC 서비스를 제공한다.

      오브젝트가 만들어지는 방식, 시점과 전략을 다르게 가져갈 수도 있고, 부가적으로 자동생성, 오브젝트에 대한 후처리, 정보의 조합, 설정 방식의 다변화, 인터셉팅 등 오브젝트를 효과적으로 활용할 수 있는 다양한 기능을 제공한다.

    • 애플리케이션 컨텍스트는 빈을 검색하는 다양한 방법을 제공한다.

1.6 싱글톤 레지스트리와 오브젝트 스코프

스프링은 여러 번에 걸쳐 빈을 요청하더라도 매번 동일한 오브젝트를 돌려준다.

동일성과 동등성
- 동일성(identity) : 실제 인스턴스가 같다. 따라서 참조 값을 비교하는 == 비교의 값이 같다.
- 동등성(equality) : 실제 인스턴스는 다를 수 있지만 인스턴스가 가지고 있는 값이 같다. equals() 메소드 비교의 값이 같다.

싱글톤 레지스트리로서의 애플리케이션 컨텍스트

애플리케이션 컨텍스트는 싱글톤을 저장하고 관리하는 싱글톤 레지스트리이기도 하다.
스프링은 기본적으로 별다른 설정을 하지 않으면 내부에서 생성하는 빈 오브젝트를 모두 싱글톤으로 만든다.

스프링이 처음 설계됐던 대규모 엔터프라이즈 서버환경에서는 초당 수십에서 수백 번씩 요청을 받아서 처리할 수 있는 높은 성능이 요구되는 환경이었다.

요청이 올 때마다 매번 각 로직을 담당하는 오브젝트를 새로 만들어서 사용한다면, 요청 한번에 n개의 오브젝트가 새로 만들어지고 초당 500개의 요청이 들어오면 (500 * n)개의 새로운 오브젝트가 생성된다.

이런 문제를 해결하기 위해, 서블릿은 클래스당 하나의 오브젝트만 만들어두고, 사용자의 요청을 담당하는 여러 스레드에서 하나의 오브젝트를 공유해 동시에 사용한다.

  • 애플리케이션 안에서 제한된 수, 대개 한 개의 오브젝트만 만들어서 사용하는 것이 싱글톤 패턴의 원리다.
  • 하지만, 디자인 패턴에 소개된 싱글톤 패턴은 사용하기가 까다롭고 여러 가지 문제점이 있다.
    • private 생성자를 갖고 있기 대문에 상속할 수 없다.
    • 싱글톤은 테스트 하기 힘들다.
    • 서버환경에서는 싱글톤이 하나만 만들어지는 것을 보장하지 못한다.
    • 싱글톤의 사용은 전역 상태를 만들 수 있기 때문에 바람직하지 못하다.
  • 스프링 레지스트리 : 자바의 기본적인 싱글톤 패턴의 구현 방식은 여러 가지 단점이 있기 때문에, 스프링은 직접 싱글톤 형태의 오브젝트를 만들고 관리하는 기능을 제공한다.
  • 싱글톤 레지스트리의 장점은 static 메소드와 private 생성자를 사용해야 하는 비정상적인 클래스가 아니라, 평범한 자바 클래스를 싱글톤으로 활용하게 해준다는 점이다.

싱글톤과 오브젝트의 상태

  • 싱글톤은 멀티스레드 환경에서 여러 스레드가 동시에 접근해서 사용할 수 있다. 상태관리에 주의해야 한.
  • 기본적으로 싱글톤이 멀티스레드 환경에서 서비스 형태의 오브젝트로 사용되는 경우에는 상태정보를 내부에 갖고 있지 않은 무상태 방식으로 만들어져야 한다.

다중 사용자의 요청을 한꺼번에 처리하는 스레드들이 동시에 싱글톤 오브젝트의 인스턴스 변수를 수정하는 것은 매우 위험하다. 저장할 공간이 하나뿐이니 서로 값을 덮어쓰고 자신이 저장하지 않은 값을 읽어올 수 있기 때문이다.

  • 따라서 싱글톤은 기본적으로 인스턴스 필드의 값을 변경하고 유지하는 상태유지 방식으로 만들지 않는다.
  • 스프링의 싱글톤 빈으로 사용되는 클래스를 만들 때는 개별적으로 바뀌는 정보는 로컬 변수로 정의하거나, 파라미터로 주고받으면서 사용하게 해야 한다.
  • 다만, 읽기전용의 값이라면 초기화 시점에서 인스턴스 변수에 저장해두고 공유해도 문제가 되지 않는다.

스프링 빈의 스코프

  • 스프링 빈의 기본 스코프는 싱글톤이다.
  • 스프링에서 만들어지는 대부분의 빈은 싱글톤 스코프를 갖는다.

    Scope Description
    singleton (기본값) 스프링 IoC 컨테이너당 하나의 인스턴스만 사용
    prototype 매번 새로운 빈을 정의해서 사용
    request HTTP 라이프사이클 마다 하나의 빈을 사용
    session HTTP 세션마다 하나의 빈을 사용
    application ServeltContext 라이프사이클 동안 하나의 빈만 사용
    websocket websocket 라이프사이클 안에서 하나의 빈만 사용

Reference

[Spring] 제어의 역전(IoC) 스프링 컨테이너와 스프링 빈