AOP와 @Transactional 의 원리

정훈희·2022년 11월 12일
0

Spring

목록 보기
21/24
post-thumbnail

참조

스프링의 프록시 팩토리 빈

스프링은 프록시 팩토리빈을 편리하게 만들 수 있게 도와주는 추상레이어를 제공한다.

바로 ProxyFactoryBean이다.

ProxyFactoryBean은 순수하게 프록시 생성 작업만을 담당하고, 부가기능은 별도의 빈에 둘 수 있다.

부가기능은 Methodlnterceptor인터페이스를 구현해서 만든다.

InvocationHandler와 비슷하지만 다른점은 InvocationHandler의 invoke() 메소드는 타깃오브젝트의 정보를 제공하지 않지만, Methodlnterceptor는 ProxyFactoryBean으로부터 타깃 오브젝트에 대한 정보까지 함께 제공받는다.

→ Methodlnterceptor는 타깃 오브젝트에 대한 정보까지 함께 제공받기 때문에 타깃오브젝트에 상관없이 독립적으로 만들어질 수 있고, 타깃이 다른 여러 프록시에서 함께 사용할 수 있고, 싱글톤 빈으로 등록 가능하다.

  • ProxyFactoryBean을 이용한 다이내믹 프록시 테스트
    @Test
    public void proxyFactoryBean() (
    	ProxyFactoryBean pfBean = new ProxyFactoryBean();
    	pfBean.setTarget(new HelloTarget());
    	// 부기기능을 담은 어드바이스를 추가, 여러개 추가 가능
    	pfBean.addAdvice(new UppercaseAdvice());
    	// FactoryBean이므로 getObject로 생성된 프록시를 가져온다.
    	Hello proxiedHello = (Hello)pfBean.getObject();
    	assertThat(proxiedHello.sayHello("Toby", is("HEllO TOBY"));
    	assertThat(proxiedHello.sayHi("Toby", is("HI TOBY"));
    	assertThat(proxiedHello.sayThankYou("Toby", is("THANK YOU TOBY"));
    }
    
    static class UppercaseAdvice implements MethodInterceptor {
    	public Object invoke(MethodInvocation invocation) throws Throwable {
    		String ret = (String)invocation.proceed();
    		return ret.toUpperCase();
    	}
    }
    
    ...

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

Methodlnterceptor처럼 타깃 오브젝트에 적용하는 부가기능을 담은 오브젝트를 스프링에서는 어드바이스라고 부른다. 어드바이스는 타깃 정보를 갖고있지 않기 때문에 타깃오브젝트에 상관없이 독립적으로 만들어질 수 있고, 부가기능에만 집중할 수 있다.

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

포인트 컷이란 메소드 선정 알고리즘을 담은 오브젝트를 뜻한다.

전체적인 과정을 살펴보자.

프록시는 클라이언트로부터 요청을 받으면 포인트컷에게 부가기능을 부여할 메소드인지 확인해달라고 요청한다.

프록시는 포인트컷에게 부가기능 적용 대상 메소드인지를 확인받으면, 어드바이스를 호출한다.

어드바이스는 직접 타깃을 호출하지 않고, 타깃 정보를 가지고있지 않다. 어드바이스가 부가기능을 부여하는 중에 타깃 메소드의 호출이 필요하면 프록시로부터 받은 Methodlnvocation 타입 콜백 오브젝트의 proceed() 메소드를 호출하면 된다.


자동 프록시 생성

위의 기술들을 적용하며 많은 문제를 해결했 지만 아직 한가지 문제가 남았다.

부가기능의 적용이 필요한 타깃 오브젝트마다 비슷한 내용의 빈 설정정보를 추가해주는 부분이다.

→ 자동 프록시 생성기

→ 빈 후처리기를 이용한 자동 프록시 생성기

빈후처리기 자체를 빈으로 등록 → 빈 오브젝트가 생성될 때마다 빈 후처리기에 보내서 후처리 작업을 요청 → 빈 후처리기는 빈 오브젝트의 프로퍼티를 수정, 초기화, 오브젝트 자체를 변경하는 것도 가능

→ 스프링이 생성하는 빈 오브젝트의 일부를 프록시로 포장하고, 프록시를 빈으로 대신 등록할 수 있다. → 자동 프록시 생성 빈 후처리기

즉, 타깃 오브젝트의 빈이 생성될 때 빈 후처리기로 타깃의 빈을 프록시로 바꿔치기가 가능하다.


UserService의 트랜잭션 적용 과정

  • 트랜잭션 서비스 추상화 트랜잭션을 적용하다 보니 특정 트랜잭션 기술에 종속됨 → 트랜잭션 추상화: 인터페이스와 DI를 통해 무엇을 하는지는 남기고, 그것을 어떻게 하는지를 분리 → 비즈니스 로직 코드에는 영향을 주지 않고 독립적으로 변경가능해짐
  • 프록시와 데코레이터 패턴 트랜잭션을 다루는 코드는 추상화로 제거했지만 트랜잭션을 적용하고 있다는 사실은 남아있음 → 데코레이터 패턴으로 비즈니스 로직 클래스의 코드에는 영향을 주지 않으며 트랜잭션이라는 부가기능을 자유롭게 부여할 수 있는 구조를 만들었음 → 비즈니스 로직 코드는 트랜잭션과 같은 성격이 다른 코드로부터 자유로워짐 + 고립된 단위 테스트를 만들 수 있게됨
  • 다이내믹 프록시와 프록시 팩토리 빈 프록시를 통해 비즈니스 로직 코드에서 트랜잭션 코드는 전부 제거 But 비즈니스 로직 인터페이스의 모든 메소드에다 트랜잭션 기능을 부여하는 코드를 넣음(코드 중복) → 다이내믹 프록시 기술 적용 → 스프링의 프록시 팩토리 빈 덕분에 부가기능을 담은 어드바이스와 부가기능 선정 알고리즘을 담은 포인트컷은 프록시에서 분리됨 → 여러 프록시에서 공유할 수 있게됨
  • 자동 프록시 생성 방법과 포인트컷 트랜잭션 적용 대상이 되는 빈 마다 일일히 프록시 팩토리 빈을 설정해줘야 하는 문제가 있음 → 빈 생성 후처리 기법을 통해 자동 프록시 생성기 도입 → 프록시 적용 대상을 일일히 지정하지 않고 패턴을 이용해 자동으로 선정할 수 있도록 확장된 포인트컷을 사용 → 간단한 설정만으로 적용 대상을 쉽게 선택 가능해짐

그래서 AOP는 무엇일까?

기존 객체지향 기술의 설계방법으로는 독립적인 모듈화가 불가능한 부가기능 모듈은 객체지향 기술에서 사용하는 오브젝트와는 다르게 에스펙트(aspect)라고 부른다.

에스펙트란 그 자체로 애플리케이션의 핵심기능을 담고 있지는 않지만, 애플리케이션을 구성하는 중요한 한가지 요소이고 핵심기능에 부가되어 의미를 갖는 특별한 모듈을 가리킨다.

에스펙트는 부가될 기능을 정의한 코드인 어드바이스와 어드바이스를 어디에 적용할지를 결정하는 포인트컷을 함께 갖고있다.

애플리케이션의 핵심적인 기능에서 부가적인 기능을 분리해서 애스펙트라는 독특한 모듈로 만들어서 설계하고 개발하는 방법을 애스펙트 지항 프로그래밍(Aspect Oriented Programming) 또는 약자로 AOP라고 부른다.

이름만 들으면 마치 OOP가 아닌 다른 프로그래밍 언어 또는 패러다임이라고 느껴지지만 AOP는 OOP를 돕는 보조적인 기술이지 OOP를 완전히 대체하는 새로운 개념은 아니다.

사용자 관리 로직이라는 관점 대신 트랜잭션 경계설정이라는 관점에서 바라보고 그 부분에 집중해서 설계하고 개발할 수 있게 해주는 것 처럼, AOP는 애플리케이션을 특정한 관점을 기준으로 바라볼 수 있게 해준다.


트랜잭션 어노테이션

트랜잭션 어노테이션은 메소드, 클래스, 인터페이스를 대상으로 사용할수 있다.

@Transactional 애노테이션을 트랜잭션 속성정보로 시용 하도록 지정하면 스프링은 @Transactional이 부여된 모든 오브젝트를 자동으로 타깃 오브젝트로 인식한다.

즉, @Transactional은 기본적으로 트랜잭션 속성을 정의하는 것이지만, 동시에 포인트컷의 자동등록에도 사용된다.

위 그림은 @Transactional 어노테이션을 사용했을 때 어드바이저의 동작방식을 보여준다.

  • 어드바이저: 포인트컷과 어드바이스를 하나씩 가지고 있는 오브젝트다. 즉 어떤 기능을 어디에 전달할 것인가를 알고 있는 스프링 AOP의 가장 기본이 되는 모듈이다.

Txlnterceptor는 @Transactional 어노테이션의 엘리먼트에서 트랜잭션 속성을 가져 오는 AnnotationTransactionAttributeSource를 시용한다.

동시에 포인트컷도 @Transactional을 통한 트랜잭션 속성정보를 참조하도록 만든다. @Transactional로 트랜잭션 속성이 부여된 오브젝트라면 포인트컷의 선정 대상이기도 하기 때문이다.

이 방식을 이용하면 포인트컷과 트랜잭션 속성을 어노테이션 하나로 지정할 수 있다.

BUT 트랜잭션 부가기능 적용 단위는 메소드 → 동일한 속성 정보를 가진 어노테이션을 반복적으로 메소드에 부여해주게 될 수 있음

→ 타깃 메소드, 타깃 클래스, 선언 메소드, 선언 타입(클래스, 인터페이스) 순서에 따라서 @Transactional이 적용됐는지 차례로 확인하고, 가장 먼저 발견되는 속성정보를 사용하게 하는 방법으로 해결할 수 있다.

트랜잭션 기능이 담긴 어드바이스는 이미 등록이 되어있고, @Transactional을 타깃에 명시하면 포인트컷 정보로 등록된다. 그리고 이 어드바이스와 포인트컷을 가지는 어드바이저는 Bean으로 등록된다.

Bean 후처리기는 타깃 Bean이 생성된 직후 어드바이저 Bean을 조회 후 생성된 타깃 Bean에 어느바이스가 적용될 지 포인트컷으로 판단하고 판단 결과에 따라 타깃 Bean에 프록시 객체로 대신 치환한다.

→ 클라이언트는 타깃 Bean을 주입받고 타깃의 메서드를 호출하는것 처럼 보이겠지만 실제는 프록시 객체를 주입받고 프록시 객체 메서드를 호출하게 되는것 이다.

요약

  1. @Transactional 을 적용시키면 포인트컷에 타깃 정보 전달
  2. 트랜잭션 기능을 가진 어드바이스와 포인트컷을 가지는 어드바이저는 Bean으로 등록
  3. Bean 후처리기는 타깃 Bean이 생성된 직후 어드바이저 Bean을 조회
  4. 생성된 타깃 Bean에 어느 어드바이스가 적용될 지 포인트컷으로 판단
  5. 판단 결과에 따라 타깃 Bean을 프록시 객체로 바꿈
  6. 클라이언트는 타깃 Bean을 주입받고 메소드를 호출하는 것 처럼 보이지만 실제론 프록시 객체를 주입받고 프록시 객체의 메소드를 호출하게 됨
profile
DB를 사랑하는 백엔드 개발자입니다. 열심히 공부하고 열심히 기록합니다.

0개의 댓글