본문 바로가기

IT

스프링 트랜잭션 (Spring Transaction) step5 : 빈 후처리기 DefaultAdvisorAutoProxyCreator

728x90

이전 step4에서 고민한 자동 프록시 생성을 빈 후처리기를 이용한다.

BeanPostProcessor 인터페이스를 구현해서 만든 빈 후처리기를 이용하면 스프링 빈 오브젝트로 만들어지고 난 후에 빈 오브젝트를 다시 가공할 수 있게 해준다. 빈 후처리기 중의 하나인 DefaultAdvisorAutoProxyCreator를 사용하여 문제를 해결한다. 빈 후처리기를 스프링에 적용하는 방법은 빈 후처리기 자체를 빈으로 등록하면 빈 오브젝트가 생설될 때마다 빈 후처리기에 보내서 후처리 작업을 요청한다. 이를 잘 이용한다면 타깃 빈 오브젝트의 일부를 프록시로 포장하고 프록시를 빈으로 대신 등록하는 작업이 가능하다. 이것이 자동 프록시 생성 빈 후처리기다. 

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

1, 2.  DefaultAdvisorAutoProxyCreator 빈 후처리기가 등록되어 있으면 스프링은 빈 오브젝트를 만들 때 마다 후처리기에 빈을 보낸다. 3. DefaultAdvisorAutoProxyCreator는 빈으로 등록된 모든 어드바이저 내의 포인트컷을 이용해 전달받은 빈이 프록시 적용 대상인지 확인한다.

4, 5. 프록시 적용 대상이라면 내장된 프록시 생성기에게 현재 빈에 대한 프록시를 만들고 어드바이저를 연결해준다.

6. 빈 후처리기는 프록시가 생성되면 원래 컨테이너가 전달해준 빈 오브젝트를 대신 프록시 오브젝트를 컨테이너에 돌려준다. 컨테이너는 최종적으로 빈 후처리기가 돌려준 프록시 오브젝트를 빈으로 등록하고 사용한다.

 

이전과 다르게 포인트컷이 메소드 중 어떤 메소드에 적용할 것인지가 아닌 어떤 클래스에 적용할 것인지 판단한다.

실제 Pointcut 인터페이스는 두 가지 타입의 오브젝트 선별 로직이 담겨있다.

public interface Pointcut{
	ClassFilter getClassFilter(); -> 프록시를 적용할 클래스인지 확인해준다.
    MethodMather getMethodMacher(); -> 어드바이스를 적용할 메소드인지 확인해준다.
}

DefaultAdvisorAutoProxyCreator는 등록된 빈 중에서 Advisor 인터페이스를 구현한 것을 모두 찾는다. 그리고 생성되는 모든 빈에 대해 어드바이저의 포인트컷을 적용해보면서 프록시 적용 대상을 선정해 프록시를 만들어 바꿔치기하고 원래 빈 오브젝트는 프록시 뒤에 연결돼서 프록시를 통해서만 접근 가능하게 바뀌는 것이다. 

 

이제 직접 설정해보도록 하자.

다른 빈에서 참조되거나 빈 이름으로 조회될 필요가 없기에 간단하게 DefaultAdvisorAutoProxyCreator를 등록한다.

<bean class = "org.springboot.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />

기존 pointcut에 클래스를 분별하는 클래스 필터 지원 포인트컷을 등록하자.

<bean id = "transactionPointcut" class = "springframework.aop.support.NameMatchMethodPoincut">
    <property name = "mappedClassName" value = "*ServiceImpl" /> 
	<property name = "mappedName" value = "upgrade*" />
</bean>

어드바이스와 어드바이저는 바꿀 필요가 없다. 하지만 어드바이저로서 사용되는 방법은 바뀌었다. 이전에 ProxyFactoryBean으로 등록한 빈에서 처럼 어드바이저를 명시적으로 DI하는 빈은 존재하지 않는다. 대신 어드바이저를 이용하는 자동 프록시 생성기인 DefaultAdvisorAutoProxyCreator에 의해 자동 수집되고 프록시 대상 선정 과정에 참여하며 자동생성된 프록시에 DI 되어 동작한다.

 

이제 이전 처럼 프록시에 DI 되어 간접적으로 사용된 타깃 userServiceImpl을 직접 등록할 수 있게 되었다. 빈 후처리기를 사용했기 때문에 빈을 먼저 생성하고 자동으로 프록시 오브젝트를 생성하기 때문이다.

<bean id = "userService" class = "springbook.service.UserServiceImpl">
	<property name = "userDao" ref = "userDao" />
    <property name = "mailSender" ref = "mailSender" />
</bean>

 설정 값이 프록시를 적용하기 전의 단순한 상태로 돌아왔다. !!! 

이제 클래스 명이 *ServiceImpl이면서 메소드 명이 upgrade*인 것들을 판별하여 프록시 오브젝트를 만들고 어드바이스를 실행할 것이다.

 

현재 사용된 포인트컷은 메소드의 이름과 클래스의 이름 패턴을 각각 클래스 필터와 메소드 매처 오브젝트로 비교해서 선정하는 방식이다. 필터나 매처에서 클래스와 메소드의 메타정보를 제공받아 단순한 이름을 비교하고 있지만 리플렉션 API를 이용하면 클래스와 메소드 이름 뿐만이 아닌 정의된 패키지, 파라미터, 리턴 값, 부여된 어노테이션이나 구현한 인터페이스, 상속한 클래스 등의 정보까지 알아낼 수도 있다. 이를 생각해본다면 슬슬 필자의 궁금증이 풀리기 시작했다. @Transactional 어노테이션이 적용된 메소드 또는 클래스들이 어떻게 트랜잭션이 적용되는지 상상이 가기 시작했다. 스프링은 아주 간단하고 효과적인 방법으로 포인트컷의 클래스와 메소드를 선정하는 알고리즘을 작성할 수 있는 일종의 표현식 언어 포인트컷 표현식을 제공한다. 포인트컷 표현식에 대해서 포스팅한 다양한 글들이 많으니 참고하기 바란다. 

@Transactional이 적용된 메소드의 경우 @annotation(org.springframework.transaction.annotation.Transactional) 포인트컷을 적용해준다면 선정할 수 있다. 그렇다면 Advice에 트랜잭션 관련 로직을 처리하고 어드바이저로 묶으면 된다.  

 

포인트컷 표현식을 활용하여 위 pointcut 설정을 바꿔보자.

<bean id = "transactionPointcut" class = "org.springframework.aop.aop.aspectj.AspectJExpressionPointcut ">
    <property name = "expression" value = "execution(* *..*ServiceImpl.upgrade*(..))" />
</bean>

포인트컷은 스프링 개발팀이 제공하는 스프링 지원툴을 사용하면 간단히 포인트컷이 선정한 빈이 어떤 것인지 확인하는 방법이 있으므로 너무 고민하지 않아도 된다.

 

관심사가 같은 코드를 분리해 한데 모으는 것은 소프트웨어 개발의 기본이 되는 원칙이다.

코드를 분리하고, 한데 모으고, 인터페이스를 도입하고, DI를 통해 런타임 시에 의존관계를 통해 대부분의 문제를 해결할 수 있지만 트랜잭션 적용 코드는 간단하게 분리해서 독립된 모듈로 만들 수가 없었다. 

왜냐하면 트랜잭션 경계설정 기능은 다른 모듈의 코드에 부가적으로 부여되는 기능이기 때문이다. 새로운 구현 기능을 가진 오브젝트를 다이내믹하게 만들어내는 다이내믹 프록시, DI/IoC 컨테이너의 빈 생성 작업을 가로채서 빈 오프젝트를 프록시로 대체하는 빈 후처리 기술과 같은 복잡한 기술로 이를 해결하였다.

그리하여 TransactionAdvice를 만들어 독립적으로 모듈화될 수 있었다. 이 코드는 중복되지 않으며 재사용이 가능하며 변경이 필요하면 유연하게 대응이 가능하다. 핵심기능을 담은 코드와 설정에는 전혀 영향을 주지 않아도 된다. 결국 지금까지의 방법은 효과적으로 모듈화하는 방법을 찾는 것이었고 어드바이스와 포인트컷을 결합한 어드바이저가 단순하지만 이런 특성을 가진 모듈의 원시적인 형태로 만들어지게 됐다.

 

객체지향 기술의 설계 방법으로는 트랜잭션과 같은 부가기능은 모듈화가 불가능했다. 이런 부가기능 모듈화 작업은 기존의 객체지향 설계 패러다임과는 구분되는 새로운 특성이 있었고 따라서 객체지향 기술에서 주로 사용하는 오브젝트와는 다르게 특별한 이름으로 부르기 시작했으며 그것이 바로 애스펙트(Aspect)이다. 

 

여기까지 왔으면 AOP에 대해 궁금증이 생기지 않는가? 

 

 

 

 

 

 

 

 

참고 자료 - 토비의 스프링 3.1 | 저자 이일민