본문 바로가기
책 정리/토비의 스프링

6.2. 스프링 AOP

by 이석준석이 2021. 2. 10.

1. 자동 프록시 생성

 

중복 문제 제거하기

  • 매번 ProxyFactoryBean 들을 복사하여 등록해야하는 문제 (실수하기 쉽다.)

해결법 1. 빈 후처리기 (BeanPostProcessor) 를 이용한 자동 프록시 생성기

  • BeanPostProcessor 인터페이스를 구현 (DefaultAdvisorAutoProxyCreator) 해서 만드는 빈 후처리기
    • DefaultAdvisorAutoProxyCreator (Advisor를 이용한 자동 프록시 생성기) 를 이용해보자.

사용법 

// Advice 등록
@Bean
public TransactionAdvice transactionAdvice() {
    TransactionAdvice transactionAdvice = new TransactionAdvice();
    transactionAdvice.setTransactionManager(transactionManager());
    return transactionAdvice;
}

// 어떤 클래스타입의 경우에 적용할 지 포인트컷을 설정한다.
@Bean
public NameMatchClassMethodPointcut transactionPointCut() {
    NameMatchClassMethodPointcut pointcut = new NameMatchClassMethodPointcut();
    pointcut.setMappedName("upgrade*");
    pointcut.setMappedClassName("*ServiceImpl");
    return pointcut;
}

// Advisor 등록
@Bean
public DefaultPointcutAdvisor transactionAdvisor() {
    return new DefaultPointcutAdvisor(transactionPointCut(), transactionAdvice());
}

// 빈 후처리기를 빈으로 등록하면, 자동으로 Advisor를 호출하여 pointcut에 부합한다면 advice 호출
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
    return new DefaultAdvisorAutoProxyCreator();
}

 

포인트컷은 메소드만 검증하는 NameMatchMethodPointcut을 상속한 뒤, 필터를 추가한 클래스를 사용하였다.

public class NameMatchClassMethodPointcut extends NameMatchMethodPointcut {
    public void setMappedClassName(String mappedClassName) {
        this.setClassFilter(new SimpleClassFilter(mappedClassName));
    }

    static class SimpleClassFilter implements ClassFilter {
        private String mappedName;

        private SimpleClassFilter(String mappedName) {
            this.mappedName = mappedName;
        }
        @Override
        public boolean matches(Class<?> clazz) {
            return PatternMatchUtils.simpleMatch(mappedName, clazz.getSimpleName());
        }
    }
}

2. 포인트컷 표현식

 

AspectJExpressionPointcut 클래스를 사용한다.

 

표현식

  • execution([접근제한자 패턴] 리턴타입패턴 [풀패키지네임.] 메소드이름 (파라미터 타입) [throws 예외])
  설명 생략가능여부
접근제한자 패턴 private / public / protected 등 접근제한자 생략가능
리턴타입 패턴 해당 함수의 리턴타입
*(asterisk) 을 사용해서 모든 타입이 가능하다고 명시할 수 있다.
생략 불가능
풀패키지네임. 클래스의 풀 패키지네임을 명시한다.

생략하면 모든 것을 허용하겠다는 뜻이다.

(.) -> 구분자
(*) -> 패키지 이름이나 인터페이스 이름에 *를 사용하여 뒤에 어떤이름이든 올 수 있도록 함
(..) -> 특정 패키지의 서브패키지를 모두 포함할 수 있다.
생략가능
메소드이름 메소드 이름 패턴이다. 
모든 메소드를 다 가능하게 하려면 (*) 을 사용한다.
생략 불가능
파라미터 타입 파라미터가 없는 메소드를 지정하고 싶다면 () 를 사용한다.

(..) -> 모든 파라미터 허용
(...) -> 뒷부분의 파라미터 조건 생략 가능
생략 불가능
throw 예외 예외 이름 패턴 생략 가능

 

예시)

  • execution(* minus(int, int))
    • 모든 리턴타입을 허용 / minus(int, int) 함수에 대한 포인트컷을 설정한다.
  • execution(* minus(..)) 
    • 모든 리턴타입 허용 / minus(파라미터 자유) 에 대한 포인트컷을 설정한다.
  • execution(* *(..)) 
    • 모든 메소드를 허용하는 포인트컷

execution 이외에도..

  • bean(*Service)
    • Service로 끝나는 모든 빈에 대한 포인트 컷 설정
  • @annotation(org.springframework.transaction.annotation.Transactional)
    • @Transactional 어노테이션이 적용된 메소드에 대한 포인트 컷 설정

3. AOP란?

  • 부가적인 기능을 분리해서 Aspect 라는 독립적인 모듈로 만들어서 설계하고 개발하는 방식

적용 기술

  • Proxy 를 이용한 AOP
    • 지금까지 위에서 한 것 (Runtime weaving) / ProxiedFactoryBean
  • 바이트코드 생성과 조작을 통한 AOP (AspectJ)
    • 컴파일된 TargetClass 파일을 수정하거나 / 바이트코드를 조작한다.

스프링의 Proxy 방식 AOP 를 사용하려면 최소 4개의 빈을 등록해야한다.

  • 자동 프록시 생성기
    • BeanPostProcessor
  • 어드바이스
    • 토비의 스프링 책에서는 MethodInterceptor를 구현한 TransactionAdvice 사용
  • 포인트컷
    • 토비의 스프링 책에서는 AspectJExpressionPointcut을 사용
  • 어드바이저
    • 토비의 스프링 책에서는 DefaultPointcutAdvisor를 등록해서 사용
    • 자동 프록시 생성기에 의해서 자동으로 검색되어 사용된다.

4. 트랜잭션 속성

 

현재 TransactionAdvice 코드의 일부분

TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());

try {
    Object ret = invocation.proceed();
    transactionManager.commit(status);
    return ret;
} catch (RuntimeException e) {
    transactionManager.rollback(status);
    throw e;
}

 

DefaultTransactionDefinition은 무엇인가?

  • TransactionDefinition Interface를 구현하는 구현체이며, 트랜잭션 동작방식에 영향을주는 네가지 속성을 정의한다.

1. 트랜잭션 전파

  • 트랜잭션 경계에서 이미 진행중인 트랜잭션이 있을 때, 없을 때 어떻게 동작할 것인지를 결정하는 방식이다.
PROPAGATION_REQUIRED 진행중인 트랜잭션이 없으면 새로 시작하며, 
진행중인 트랜잭션이 있다면 해당 트랜잭션에 참여한다.

DefaultTransactionDefinition.class의 트랜잭션 전파 레벨PROPAGATION_REQUIRED 이다.
PROPAGATION_REQUIRES_NEW 항상 새로운 트랜잭션을 시작한다.

항상 새로운 트랜잭션을 만들어서 독립적으로 동작하게 한다.
PROPAGATION_NOT_SUPPORTED 트랜잭션없이 동작하도록 설정한다.

진행중인 트랜잭션이 있더라도 무시한다.

 

2. 격리수준

  • 기본적으로 데이터베이스에 Isolation Level 이 설정되어있지만, 트랜잭션 단위로 격리수준을 조정할 수 있다.
  • DefaultTransactionDefinition.class 의 격리수준ISOLATION_DEFAULT (DataSource 에 설정된 기본 격리수준을 따름)

 

3. 제한시간

  • 트랜잭션을 수행하는 제한시간을 설정할 수 있다.
  • DefaultTransactionDefition.class 의 기본 설정은 제한시간이 없는 것이다.

 

4. 읽기전용

  • read-only 로 설정하면 트랜잭션 내에서 데이터 write 의 시도를 막을 수 있다.

DefaultTransactionDefinition.class 는 기본적인 옵션이 이미 적용되어있지만, 함수마다 다른 트랜잭션 속성을 적용시키고 싶다면?

  • MethodInterceptor => TransactionInterceptor을 이용 (Methodinterceptor + 메소드 이름패턴 제공)

 

TransactionInterceptor

프로퍼티 1. TransactionManager : 어떤 TransactionManager로 트랜잭션을 수행할 것인지

2. Properties : 메소드이름 패턴에대해서 어떤 Transaction 속성을 줄 것인지
예외상황 RuntimeException : 롤백
CheckedException : 롤백하지 않음

rollbackOn 속성을 통해서 기능을 추가할 수 있다.
이름패턴을 이용한
속성 지정
"PROPAGATION_NAME,ISOLATION_NAME,readOnly,timeout_????,-Exception1,+Exception2"

-Exception : 롤백 대상에 추가
+Exception : 롤백 대상에서 제거

 

TransactionInterceptor 로 등록한 Bean

@Bean
public TransactionInterceptor transactionAdvice() {
    Properties properties = new Properties();
    properties.put("get*", "PROPAGATION_REQUIRED,readOnly,timeout_30");
    properties.put("upgrade*", "PROPAGATION_REQUIRES_NEW,ISOLATION_SERIALIZABLE");
    properties.put("*", "PROPAGATION_REQUIRED");

    TransactionInterceptor transactionInterceptor = new TransactionInterceptor();
    transactionInterceptor.setTransactionManager(transactionManager());
    transactionInterceptor.setTransactionAttributes(properties);
    return transactionInterceptor;
}

프록시 방식 AOP 로 생성된 프록시에서 타깃 오브젝트 내의 메소드를 호출하는 경우에는 적용되지 않는다. (mommoo.tistory.com/92)

  • 위의 글은 바이트코드로 된 설명이나, 프록시의 경우에도 같은 방식으로 적용이 되지 않는다.

5. 트랜잭션 어노테이션

 

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
}

 

@Target

  • Type(클래스, 인터페이스) / Method(메소드) 에 사용할 수 있는 어노테이션이다.

@Retention

  • 런타임 시점까지 유지되므로, 리플렉션을 통해서 정보를 얻을 수 있다.

@Inherited

  • 상속을 통해서도 어노테이션 정보를 얻을 수 있다.

스프링은

  • Pointcut : @Transactional 어노테이션이 붙은 오브젝트를 타깃 오브젝트로 인식한다. (TransactionAttributeSourcePointcut)
  • Advice : AnnotationTransactionAttributeSource 를 통해서 어노테이션에 속성정보를 참조한다.

 

'책 정리 > 토비의 스프링' 카테고리의 다른 글

8. 스프링이란 무엇인가  (0) 2021.02.20
7. 스프링 핵심 기술의 응용  (0) 2021.02.14
6.1. AOP  (0) 2021.02.02
5. 서비스 추상화  (0) 2021.02.01
4. 예외  (0) 2021.01.30