6장 AOP

Soonwoo Kwon·2022년 3월 28일
0

토비의 스프링

목록 보기
6/11

6.1 트랜잭션 코드의 분리(1)

메소드 분리

  • UserService 클래스 내부에는 비즈니스 로직 뿐만 아니라 트랜잭션 경계 설정 코드가 포함되어 있다.
  • 목적이 다른 두 기능이 한 클래스에 공존하지 않도록 두 기능을 독립시킨다.
  • 비즈니스 로직과 관련된 부분을 upgradeLevelInternal() 메소드로 분리시킨다.

DI를 이용한 클래스의 분리

  • 비즈니스 로직과 트랜잭션 경계 설정 부분을 다른 메소드로 분리하였지만 여전히 UserService 클래스에 공존한다.
  • 기존 UserService 클래스는 구체적인 구현 클래스이기 때문에 DI를 적용하기 어렵다.
  • 따라서 UserSercive 클래스를 추상 클래스로 바꾸어 UserService를 구현하는 방식을 적용하고 이 과정에서 DI를 적용한다.
  • 이를 통해 기존 Client(UserServiceTest)와 UserService 사이의 강한 결합도를 약한 결합으로 바꾸어 줄 수 있다.

UserService 인터페이스 도입

  • 트랜잭션 경계설정 코드는 제외한 UserService의 기존 메소드를 선언한다.
  • UserServiceImpl 클래스에서 UserService의 메소드를 구현한다.

분리된 트랜잭션 기능

  • UserServiceTx 클래스를 통해 UserService를 구현한다.
  • 이 과정에서 기존 UserService에 선언된 비즈니스 로직과 관련된 메소드는 수정자 메소드를 통한 DI를 적용하여 UserService를 구현한 오브젝트를 파라미터로 받아 이 오브젝트의 메소드를 사용하도록 한다. 이 방법을 통해 파라미터로 받은 UserService 오브젝트에게 기능을 위임한다.
  • 기존 트랜잭션 경계 설정과 관련된 메소드를 추가로 구현한다.

트랜잭션 적용을 위한 DI 설정

  • Client(UserServiceTest)는 UserServiceTx에 의존하게 되고, UserServiceTx는 UserServiceImpl에 의존하게 된다.

트랜잭션 분리에 따른 테스트 수정

  • @Autowired 어노테이션을 사용하면 기본적으로 타입이 일치하는 빈을 찾아주게 된다.
  • UserService와 같은 추상 클래스에 @Autowired를 적용하여도 알맞은 빈을 찾아줄 수 있다.
  • 하지만 UserService를 구현한 구체 클래스가 두 개 존재하기 때문에 필드 이름을 통해 빈을 찾게 된다.

트랜잭션 경계설정 코드 분리의 장점

  • 비즈니스 로직을 담당하는 UserServiceImpl이 트랜잭션 경계 설정에 독립적이다.

6.2 고립된 단위 테스트

테스를 할 때 가장 편하고 좋은 방법은 가능한 한 작은 단위로 나누어서 테스트 하는 것이다. 작은 단위로 테스트를 하게 되면 실패 원인을 쉽게 찾을 수 있다. 또한 테스트에 소요되는 시간도 줄어들게 된다.

단위 테스트와 통합 테스트

  • 단위 테스트는 정해진 단위 별로 나누어 테스트 하는 방식으로 가장 권장되는 방법이다.
  • 통합 테스트는 성격과 계층이 다른 오브젝트를 연동하거나, 외부 DB 파일과 같은 리소스들이 참여하는 테스트 방법이다.

6.3 다이내믹 프로시와 팩토리 빈

프록시와 프록시 패턴, 데코레이터 패턴

트랜잭션 경계 설정 코드를 부가기능, 비즈니스 로직을 핵심기능으로 분리하였다. 따라서 클라이언트가 핵심 기능에 접근하기 위해서는 부가기능을 거쳐서 접근하게 된다.

프록시

  • 프록시란 클라이언트가 사용하려는 실제 대상인 것처럼 위장해서 클라이언트의 요청을 받아주는 대리자이다. 프록시는 타깃과 같은 인터페이스를 구현한다.
  • 프록시는 사용 목적에 따라 두 가지로 구분한다.
    • 클라이언트가 타깃에 접근하는 방법을 제어한다.
    • 타깃에 부가적인 기능을 부여해준다.

타깃(target), 실체(real subject)

  • 타깃, 실체는 최종적으로 요청을 위임받아 처리하는 실체 오브젝트이다.

데코레이터 패턴

  • 데코레이터 패턴은 타깃에 부가적인 기능을 런타임 시 다이내믹하게 부여해주기 위해 프록시를 사용하는 패턴이다.
  • 프록시가 하나로 제한되지 않는다.
  • 각 데코리이터 프록시는 기능을 위임하는 대상에도 인터페이스로 접근하기 때문에 자신이 위임하는 대상에 대해 알 수 없다. 런타임 시에 정해진다.
  • UserServiceTx는 UserService를 구현하여 UserServiceImpl에 트랜잭션 경계 설정이라는 부가적인 기능을 추가한 데코레이터이다.

프록시 패턴

  • 타깃에 대한 접근 방법을 제공하기 위해 프록시를 사용하는 패턴이다.
  • 타깃 오브젝트가 생성하기 복잡하거나 당장 필요하지 않은 경우 오브젝트 생성을 최대한 늦추는 것이 좋다.
  • 타깃 오브젝트를 실제 생성하지 않고 프록시를 전달한 후, 프록시의 메소드를 통해 타깃 오브젝트를 생성하고 요청을 위힘한다.
  • 프록시가 자신이 위임하는 클래스 정보를 알고 있는 경우가 많고, 인터페이스를 통해서도 접근 가능하다.

다이태믹 프록시

프록시를 만들기 번거로운 이유로 다음 두 가지가 있다.

  • 타깃의 인터페이스를 구현하고 위임하는 코드를 작성하기가 번거롭다.
  • 부가기능 코드가 중복될 가능성이 많다.

리플렉션

  • 프록시를 만들기 번거로운 문제를 해결하기 위해 JDK의 다이내믹 프록시를 적용할 수 있다.
  • 다이내믹 프록시는 리플렉션 기능을 이용해서 만들수 있다.

다이내믹 프록시 적용

  • 부가기능은 프록시 오브젝트와 독립적으로 InvocationHandler를 구현한 오브젝트에 담는다.
    • InvocationHandler 클래스는 invoke() 메소드 만을 갖는 추상 클래스이다.
    • invoke() 메소드는 리플렉션의 Method 인터페이스를 파리미터로 받는다.
  • 프록시 생성에 필요한 파라미터
    • 클래스 로더
    • 구현할 인터페이스의 배열
    • InvocationHandler 구현 오브젝트

다이내믹 프록시의 확장

  • 다이내믹 프록시 방식이 직접 만든 프록시보다 좋은 점이 두 가지 있다.
    • 메소드의 개수가 늘어날 때 마다 프록시 클래스를 직접 구현하지 않아도 된다.
    • InvocationHandler는 타깃의 종류에 상관 없이 적용 가능하다.
    • 리턴 타입과 메소드의 이름까지도 조건으로 활용할 수 있다.

다이내믹 프록시를 위한 팩토리 빈

스프링은 내부적으로 리플렉션 API를 이용해서 빈 정의에 나오는 클래스 이름을 통해 빈 오브젝트를 생성한다. 하지만 다이내믹 프록시의 경우 이러한 방식으로 오브젝트가 생성되지 않는다. 다이내믹 프록시는 Proxy 클래스의 newProxyInstance() 라는 스태틱 팩토리 메소드를 통해서만 만들 수 있다.

팩토리 빈

package.org.springframework.beans.factory;

public interface FactoryBean<T> {
	T getObject() throws Exception;
    Class<? extends T> getObjectType();
    boolean isSigleton();

프록시 팩토리 빈 방식의 장점과 한계

프록시 팩토리 빈 방식의 장점

  • 타깃 인터페이스 구현 클래스를 일일이 만들지 않아도 된다. 하나의 핸들러 메소드를 구현하는 것 만으로 수많은 메소드에 부가기능을 부여해 줄수 있다.
  • 팩토리 빈을 이용한 DI를 통해 다이내믹 프록시 생성 코드도 제거할 수 있다.

프록시 팩토리 빈의 한계

  • 하나의 클래스 안에 여러 메소드에 부가기능을 부여하는 것은 가능하지만, 여러 클래스의 공통적인 부가기능을 제공하는 것은 프록시 팩토리 빈의 설정이 중복되는 것을 막을 수 없다.
  • 하나의 타깃에 여러개의 부가기능을 적용하려고 할 때 설정 파일이 급격하게 복잡해 진다.
  • TransactionHandler 오브젝트가 프록시 팩토리 빈 개수만큼 만들어진다.

6.4 스프링의 프록시 팩토리 빈

ProxyFactoryBean

  • ProxyFactoryBean은 프록시를 생성하여 빈 오브젝트로 등록하게 해주는 팩토리 빈이다.
  • 순수하게 프록시를 생성하는 작업만을 담당하고, 부가기능은 MethodInterceptor 인터페이스를 구현해서 만든다.
    • MethodInterceptorInvacationHandler와는 다르게 타깃 오브젝트에 대한 정보까지 함께 제공받을 수 있다.
    • 따라서 타깃 오브젝트에 상관없이 독립적으로 만들어질 수 있다.

어디바이스: 타깃이 필요 없는 순수한 부가기능

  • 타깃 오브젝트를 따로 전달하지 않아도 된다.
  • addAdvice() 메소드를 통해 부가기능을 부여할 수 있다.
  • 프록시가 구현해야 하는 인터페이스를 제공하지 않아도 된다.

포인트컷: 부가기능 적용 대상 메소드 선정 방법

  • Pointcut 인터페이스를 구현한 오브젝트를 통해 메소드를 선별할 수 있다.

0개의 댓글