본문 바로가기

토비의 스프링 정리

토비의 스프링 - 8.4 스프링의 기술

8.4.1 스프링 기술

  • 스프링은 POJO 프로그래밍을 손쉽게 할 수 있도록 하는 세 가지 가능기술을 제공함
    1. IoC/DI
    2. AOP
    3. PSA
  • 가능기술 세 가지는 객체지향의 설계와 개발원리를 잘 적용하면 자연스럽게 만들어지는 것 이기도 함
  • 다만, 스프링은 통일성과 세련된 방법으로 자바 엔터프라이즈 전 영역에 효과적으로 적용될 수 있도록 프레임워크 형태로 제공하고 있음
  • 스프링의 기술들은 POJO 기반의 엔터프라이즈 개발을 편리하게 해주는 도구일 뿐임

8.4.1 제어역전(IoC) / 의존관계 주입(DI)

  • IoC/DI는 스프링의 가장 기본이되는 기술이자 스프링의 핵심 개발 원칙
  • 나머지 두 가지인 AOP와 PSA도 IoC/DI에 바탕을 두고 있음
  • 3대 핵심 기술은 아니지만, 템플릿/콜백 패턴이 적용된 부분도 IoC/DI가 핵심 원리임
  • IoC/DI를 사용하는 이유는 유연한 확장 때문임
  • 유연한 확장은 개방폐쇄원칙(OCP)라는 객체지향 설계 원칙으로 잘 설명될 수 있음
  • 유연한 확장이라는 장점은 OCP의 ‘확장에는 열려있다(개방)’에 해당됨
  • DI는 역시 OCP의 ‘변경에는 닫혀있다(폐쇄)’라는 말로 설명할 수 있음
  • 폐쇄 관점에서 볼 때 장점은 ‘재사용’임
  • A→B라는 의존관계에서 B는 자유롭게 변경이되고, 그 변경이 A에게 영향을 끼치지 않음
  • B관점에서 유연한 확장이고, A 관점으로 보자면 변경없이 재사용이 가능한 것임
  • DI의 활용 방법은 8개 이상임
    1. 핵심기능의 변경
    2. 핵심기능의 동적인 변경
    3. 부가기능의 추가
    4. 인터페이스의 변경
    5. 프록시
    6. 템플릿과 콜백
    7. 싱글톤과 오브젝트 스코프
    8. 테스트

8.4.2 핵심기능의 변경

  • DI의 가장 대표적인 적용 방법은 의존 대상의 구현을 바꾸는 것임
  • 예를 들어 DAO의 구현을 JDBC, 하이버네이트, JPA 등으로 바꾸는 것임
  • 즉, 구현 방식을 통째로 바꾸는 것임
  • 사용자 등급을 결정하는 정책을 DI로 분리하고, 정책 변경시 DI의 구현만 바꾸면 됨

8.4.3 핵심기능의 동적인 변경

  • 이 역시 의존 오브젝트의 핵심기능 자체를 변경하는 것임
  • 단지, 일반적 DI와 달리 동적으로 매번 다르게 변경할 수 있음
  • 일반적 DI는 기본적으로 런타임시에 의존 오브젝트를 연결해 주긴 하지만, 일단 DI가 되고 나면 그 이후로 정적인 관계를 맺어 변경되지 않음
  • 하지만 DI를 잘 이용하면 애플리케이션이 동작하는 중간에 의존 대상을 동적으로 변경할 수 있음
  • 예를 들어 DAO 하나가 여러 개의 Datasource를 의존하게 만들고, 등급에 따라 동적으로 DB 연결 방법을 달리할 수 있음
  • 사용자 등급이 VIP일 경우 더욱 빠른 DB를 사용하게 할 수 있음
  • 또 다른 예시는, 사용자별로 모두 독립적인 의존 오브젝트를 두게 만들 수 있음
  • 한번 로그인한 사용자는 계속 자신만의 오브젝트를 유지하게 하고, 서비스 오브젝트가 이를 DI 받아서 사용하게 할 수 있음
  • 핵심적인 기능이 바뀐다기보다는 기능은 같지만, 독립적인 상태 정보를 저장할 수 있는 자신만의 오브젝트를 가질 수 있다는 것임
  • 동적인 방식으로 핵심기능 변경을 하는건, 기술적으로 보자면 다이내믹 라우팅이나 프록시 오브젝트 기법을 활용하는 것임
  • 이 두 기법을 적용할 수 있는 이유는 DI가 있기 때문임

8.4.4 부가기능의 추가

  • 핵심기능은 그대로 둔 채로 부가기능을 추가할 수 있음
  • 인터페이스를 통한 의존성을 만들게 되면, 데코레이터 패턴을 적용할 수 있어 쉽게 부가기능을 추가할 수 있음

8.4.5 인터페이스의 변경

  • 때로 사용하려는 오브젝트가 가진 인터페이스가 클라이언트와 호환되지 않는 경우가 있음
  • 이렇게 클라이언트가 사용하는 인터페이스와 실제 오브젝트 사이의 인터페이스가 일치하지 않을 때 DI가 유용함
  • A가 C를 사용하려고 하지만, A는 B 인터페이스를 사용하도록 만들어져 있고 C는 B 인터페이스를 구현하지 않는 경우가 있음
  • 이때, B를 구현하면서 내부적으로 C를 호출하는 기능을 가진 어댑터 오브젝트를 만들어 A에 DI 하면 됨
  • 결국, A→B(C로 위임)→C처럼 구성됨
  • 여전히 A는 DI 덕분에 자신의 코드를 수정하지 않아도 됨
  • 이 방식은, 오브젝트 방식의 어댑터 디자인 패턴 응용임
  • 이를 더 일반화해서 아예 인터페이스가 다른 다양한 구현을 같은 방식으로 사용하도록 중간에 인터페이스 어댑터 역활을 하는 방법 서비스 추상화(PSA)
  • 이를 통해 다른 인터페이스를 가진 로우레벨의 기술을 변경하거나 확장해서 사용할 수 있음

8.4.6 프록시

  • 프록시 패턴의 전형적인 응용 방법이 있음
  • 필요 시점에 실제 사용할 오브젝트를 초기화하고 리소스를 준비하게 해주는 지연로딩(Lazy Loading)을 적용 하려면 프록시가 필요함
  • 또한, 원격 오브젝트를 호출할 때 마치 로컬에 있는 것 처럼 호출하는 원격 프록시를 적용할 때도 프록시가 필요함
  • 두 가지 모두 DI를 필요로 함

8.4.7 템플릿과 콜백

  • 템플릿/콜백 패턴은 DI의 특별한 적용 방법임
  • 변하는 부분과 변하지 않는 부분을 분리해서 템플릿과 콜백으로 만들고 DI 원리를 응용해 적용하면 코드를 간결하게 만들 수 있음
  • 콜백을 템플릿에 주입하는 방식은 DI 원리에 가장 충실한 응용 방법임
  • 콜백을 얼마든지 만들어 사용할 수 있으므로 유연한 확장성임
  • 템플릿을 한 번 만들면 재사용할 수 있으므로 OCP에 잘 맞음

8.4.8 싱글톤과 오브젝트 스코프

  • DI가 필요한 중요한 이유 중 한 가지는 DI 할 오브젝트의 생명주기를 제어할 수 있다는 것임
  • DI를 프레임워크로 이용한다는 것은 DI 대상 오브젝트를 컨테이너가 관리 한다는 것임
  • 오브젝트의 생성부터 소멸까지 DI 컨테이너가 주관하기 때문에, 오브젝트 스코프를 자유롭게 제어할 수 있음
  • 가장 기본이 되는 스코프는 역시 싱글톤
  • 여러 스레드의 요청 처리 방식은 오브젝트 개수를 제어하는 일이 가장 중요함
  • 한정된 리소스에서 요청마다 오브젝트를 만들게 되면 리소스 고갈이 발생하기 때문임
  • 전통적인 싱글톤 패턴은 오브제트에 많은 제약을 가하지만, 컨테이너가 관리하는 IoC 방식은 유용함
  • 또한, 컨테이너가 알아서 싱글톤으로 관리하기 때문에 싱글톤을 고려하지 않고 설계가 가능함
  • 만일 싱글톤이 아닌 임의의 생명주기를 갖느 오브젝트가 필요할 때도, 다양한 스코프를 갖는 오브젝트를 만들어 DI에 사용할 수 도 있음

8.4.9 테스트

  • DI의 가장 중요한 용도는 테스트임
  • 효과적인 테스트의 방법은 가능한 대사을 고립하는 것임
  • 그래야 오브젝트의 기능에 충실하게 테스트가 가능함
  • 의존 오브젝트를 대신해서 스텁 또는 오브젝트 같은 테스트 대역을 활용하면 유용함
  • 이때, DI는 수정자 메소드를 사용해 테스트 대역을 주입할 수 잇어 중요한 역할을 함

8.4.10 애스팩트 지향 프로그래밍(AOP)

  • AOP는 OOP 처럼 독립적인 프로그래밍 패러다임이 아님
  • AOP는 OOP의 애플리케이션의 요구조건과 기술적 난해함을 해결하도록 도와주는 보조적인 프로그래밍 기술임
  • AOP를 사요하면 OOP를 더욱 OOP로 만들 수 있음
  • IoC/DI를 이용해 POJO에 선언적인 엔터프라이즈 서비스를 제공할 수 있지만, 일부 서비스는 OOP만으로 POJO의 조건을 유지한 채 적용하기 힘듦
  • 이러한 문제를 해결하는데 AOP가 필요함
  • AOP의 적용 기법은 크게 두 가지로 분류할 수 있음
    1. 스프링과 같이 다이내믹 프록시를 사용하는 방법
    2. 💡 다이내믹 프록시는 리플렉션으로 프록시를 만드는 방법임[6.3.6 참고]
    3. 자바 언어의 한계를 넘어서는 언어의 확장을 이용한 방법

8.4.11 스프링과 같이 다이내믹 프록시를 사용하는 방법

  • 이 방법은 기존 코드에 영향을 주지 않고, 부가기능을 적용하게 해주는 데코레이터 패턴을 응용한 것임
  • 자바의 객체지향 패턴을 활용한 방법이기 때문에 만들기 쉽고 적용하기 간편
  • 하지만, 부가기능은 메소드 호출이 발생하는 지점에만 적용할 수 있음
  • 인터페이스와 DI를 활용하는 데코레이터 패턴이 기반이기 때문임
  • 스프링의 기본적인 AOP 구현하는 방법은 다이내믹 프록시를 이용하는 프록시 AOP 방식임

8.4.12 자바 언어의 한계를 넘어서는 언어의 확장을 이용한 방법

  • 대표적으로 AspectJ가 제공하는 AOP 지원 방식임
  • AspectJ는 바이트 코드를 조작하기 때문에 메소드 호출 뿐만이 아니라 인스턴스 생성, 필드 액세스, 특정 호출 경로를 가진 메소드 호출 등에서도 부가기능을 제공할 수 있음
  • 이런 고급 AOP 기능을 적용하려면 자바 언어와 JDK만으로 불가능 함
  • 대신, 별도의 AOP 컴파일러르 이용한 빌드 과정을 거치거나, 클래스가 메모리로 로딩될 때 그 바이트 코드를 조작하는 위빙과 같은 방법을 이용해야 함
  • 사용하기 번거롭지만, 프록시 방식의 AOP로는 할 수 없는 작업이 필요할 때는 AspectJ를 사용해야 함
  • 스프링은 프록시 방식의 AOP를 기본으로 하고 있지만, 특정 기능에서는 AspectJ를 꼭 사용해야 하는 것도 있음
  • AOP 적용 단계는 3 단계가 있음
    1. 미리 준비된 AOP 이용
    2. 전담팀을 통한 정책 AOP 적용
    3. AOP의 자유로운 이용

8.4.13 미리 준비된 AOP 이용

  • 스프링이 미리 만들어서 제공하는 AOP 기능을 그래돌 가져다 적용하는 단계임
  • 대표적인 AOP는 트랜잭션임
  • 스프링에서 트랜잭션만큼 자주 사용되지 않지만 특정 아키텍처를 선택했을 때 사용할 수 있도록 준비된 AOP 기능이 하나 더 있음
  • @Configrable 애노테이션을 이용한 도메인 오브젝트에 DI를 자동으로 적용해주는 AOP 기능임
  • 도메인 오브젝트를 전용 계층에 두고 접근하는 아키텍처 방식을 따를 때 반드시 필요함
  • @Configrable는 AspectJ를 이용한 AOP가 반드시 필요함

8.4.14 전담팀을 통한 정책 AOP 적용

  • AOP의 남발은 코드가 예상하지 못한 방식으로 흘러가 심각한 문제를 초래할 수 있음
  • 때문에, 특정 가이드라인 및 규칙에 따라 코드를 작성해야 함
  • 개발자가 정해진 가이드라인 및 규칙에 따라 작성을 하지 않을 때를 고려한 단계임
  • AOP 담당자 관리하에 보안, 로깅, 트레이싱, 성능 모니터링과 같은 정책적으로 적용할 만한 AOP를 이용하는 것임
  • 개발자가 가이드라인 및 규칙에 따라 작성을 하지 않을 때, AOP가 유용하게 쓰일 수 있음

8.4.15 AOP의 자유로운 이용

  • 개발자 스스로 AOP를 활용할 수 있는 단계임
  • 이전 단계에서는 애플리케이션 전체적으로 적용되는 정책 AOP 위주로 했다면, 이제는 개발자가 구현하는 기능에 적용하면 유용한 세부적 AOP를 이용할 수 있음

8.4.16 포터블 서비스 추상화(PSA)

  • PSA(Portable Service Abstraction)는 기술 변화에 관계없이 일관된 방식으로 기술에 접근하도록 하는 기술임
  • POJO는 기술 및 환경에 종속적이지 않아야 함
  • 스프링은 JavaEE를 기본 플랫포므로 하는 자바 엔터프라이즈 개발에 주로 사용됨
  • 따라서 JavaEE에 의존적일 수 밖에 없음
  • 특정 기술에 종속적이지 않다는 것은 그 기술을 사용하지 않는다는 것이 아닌, POJO 코드가 기술에 직접 노출되어 만들어지지 않는다는 것임
  • 직접 스프링이 제공하는 API를 사용하는 경우 추상 API를 이용해 코드를 작성해야 함
  • 구체적인 기술과 설정은 XML 파일안에서 지정함
  • 스프링의 서비스 추상화의 개념과 장점을 잘 이해했다면 때에 따라 직접 서비스 추상화 기법을 적용할 필요가 있음
  • 엔터프라이즈 개발에 사용되는 기술은 끊임없이 새로 만들어지거나 업그레이드 됨
  • 보편적인 기술은 다음 버전의 스프링에서 서비스 추상화 대상으로 포함할 가능성이 있음
  • 하지만, 굳이 다음 버전의 스프링을 기다릴 필요가 없음
  • 직접 추상 레이어를 도입하고 일관성 있는 API를 정의해서 사용하면 됨
  • 서비스 추상화를 위해 필요한 기술은 DI뿐임
  • 서비스 추상화는 단지 기술에 종속적이지 않게 하기 위해서만 사용되는 것은 아님
  • JavaMail과 같은 테스트가 어렵게 만들어진 API나 설정을 통해 주요 기능을 외부에서 제어하게 만들고 싶을 때 이용할 수 있음