[토비의 스프링] 6장 AOP (2)

빈 후처리기를 사용한 자동생성 DefaultAdvisorAutoProxyCreator

<!-- 서비스마다 반복되는 설정 -->
<bean id="userService" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target" ref="userServiceImpl" />
    <property name="interceptorNames">
        <list>
            <value>transactionAdvisor</value>
        </list>
    </property>
</bean>

 

위 설정파일을 보면 이전에 ProxyFactoryBean 을 이용해서 중복코드를 많이 줄여냈지만 그래도 여전히 프록시가 필요할때마다 설정파일이 어느정도 반복되고있다. 중복을 발견했으니 또 없애보자.

스프링에서 제공하는 애플리케이션 컨텍스트에 대한 확장포인트인 BeanPostProcessor 인터페이스를 구현하면되는데 DefaultAdvisorAutoProxyCreator가 해당 인터페이스를 구현하고 있다. 

빈후처리기?
빈으로 등록해두면 모든 빈들이 등록될때마다 후처리기에 넘어가서 특정 작업을 수행한다.

 

DefaultAdvisorAutoProxyCreator 를 빈으로 등록해두면 새로 등록되는 모든 빈에대해서 빈에 등록해둔 어드바이저의 포인트컷을 사용해서 작업을 수행할지 결정하고 해당한다면 프록시를 만들어준다.

 

포인트컷 인터페이스를 보면 클래스도 필터링 할수 있다. 이 인터페이스의 구현체를 빈으로 등록하면 된다.

public interface PointCut {
    ClassFilter getClassFilter();
    MethodMatcher getMethodMatcher();
}

 

<!-- 빈 후처리기 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
	
<!-- 부가기능 -->
<bean id="transactionAdvice" class="springbook.user.service.TransactionAdvice">
	<property name="transactionManager" ref="transactionManager" />
</bean>
	
<!-- 포인트 컷 -->
<bean id="transactionPointcut" class="springbook.user.service.NameMatchClassMethodPointcut">
	<property name="mappedClassName" value="*ServiceImpl" />
	<property name="mappedName" value="upgrade*" />
</bean>

<!-- 포인트컷 만족하는 타겟에 어떤 부가기능 줄지 묶은 어드바이저 -->
<bean id="transactionAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
	<property name="advice" ref="transactionAdvice" />
	<property name="pointcut" ref="transactionPointcut" />
</bean>

애스펙트 지향 프로그래밍이란?

애스펙트란 애플리케이션의 핵심기능은 아니지만 중요한 기능이고 핵심기능과 같이 있을때 의미를 가지는 모듈을 말한다.

일반적인 객체지향 방법으로 분리하기 어려운 트랜잭션 경계설정과 같은 부분을 애스펙트라 보는것이다. 이것도 결국 OOP 를 더 잘하기 위한 보조기능인 것

-프록시 방식

계속 살펴보던 프록시, 데코레이터, 빈 후처리 등을 사용해서 AOP를 지원하는 방법

-바이트코드조작 방식

AspectJ 가 있다. 바이트코드 조작을 통해서 직접 타깃 오브젝트를 고치는 방법.

1. 프록시를 타지않고 타겟안에서 서로 메서드 호출시엔 어드바이스를 적용할 수 없음

2. 애플리케이션 컨텍스트를 이용해야만 함

위와 같은 프록시 방식의 한계점을 극복할 수 있겠지만 그만큼 사용하기 까다롭다.

트랜잭션 속성

TransactionDefinition 이라는 인터페이스는 트랜잭션의 동작을 정하는 네가지 속성을 가지고 있다.

  1. 트랜잭션 전파속성
    • PROPAGATION_REQUIRED: 이전에 생성된 트랜잭션이 있다면 참여하고 없으면 새로 생성한다.
    • PROPAGATION_REQUIRE_NEW: 독립적인 트랜잭션을 생성한다.
    • PROPAGATION_NOT_SUPPORTED: 진행중인 트랜잭션이 있어도 무시한다.
  2. 격리수준
    • 매번 까먹는 트랜잭션 격리수준 
    • 기본설정은 DataSource에 정의된 속성을 따르게되어있다.
  3. 제한시간
    • 트랜잭션 수행 제한시간을 둘 수 있다.
  4. 읽기전용
    • 트랜잭션안에서 데이터 조작시 예외를 던짐으로서 막아줄수 있다.

트랜잭션 인터셉터

트랜잭션 동작원리를 배우기 위해 TansactionAdivce를 열심히 만들었지만 사실 스플링에선 해당기능을 TransactionInterceptor 를 통해 제공하고 있다. PlatformTransactionManager와 Properties 를 주입해주어야 하는데 Properties는 TransactionDefinition 인터페이스의 4개 속성 + rollbackOn 을 가지고 있다. 그리고 이름패턴을 통해 각각 메서드에 설정을 다르게 해줄수있다.

 기본적으로 TransactionInterceptor는 checked 예외가 나왔을땐 예외 상황을 복구했을것이라 생각하기때문에 롤백하지 않는다. 처리할수 없는 RuntimeException이 발생했을때 롤백한다. 때문에 checked 예외에 롤백시키고 싶을때 rollbackOn 을 사용할수 있다.

 

<!--  TransactionAdvice -> TransactionInterceptor -->
<bean id="transactionAdvice" class="org.springframework.transaction.interceptor.TransactionInterceptor">
    <property name="transactionManager" ref="transactionManager" />
    <property name="transactionAttributeSource">
        <props>
        	<!-- 메서드 이름별로 속성을 다르게 설정 가능 -->
            <prop key="save*">PROPAGATION_REQUIRED</prop>
            <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
        </props>
    </property>
</bean>

 

어노테이션 속성과 포인트컷

앞서 xml로 트랜잭션 설정 방법을알아봤지만 보통 @Transactional 로 많이 쓰는데 그 원리는 아래 순서

1. TransactionAttributeSourcePointcut 이 해당 어노테이션이 붙은 모든 메소드, 클래스를 타겟으로 지정

2. AnnotationTransactionAttributeSource 가 @Transactional이 가지고 있는 트랜잭션 설정정보를 가져온다.

3. TransactionInterceptor 에서 해당 설정을 사용해서 프록시를 만든다. 

 

그리고 @Transactional 은 여러군데 붙힐수 있기때문에 특정한 우선순위를 가지는데

구현 메서드 > 구현 클래스 > 인터페이스 메서드 > 인터페이스 클래스 순이다.

@Transactional // 4
public interface ITest {
    @Transactional // 3
    void test();
}
@Transactional // 2
public class Test implements ITest {
    @Transactional // 1
    @Override
    public void test() { }
}