Aspect Instantiation Models, An AOP Example

2023. 5. 24. 14:12Spring Framework/Aspect Oriented Programming with Spring

Aspect Instantiation Models

이것은 고급 주제입니다. AOP를 처음 시작하는 경우, 나중에 이 내용을 안전하게 건너뛰어도 됩니다.

기본적으로 각 aspect는 애플리케이션 컨텍스트 내에서 단일 인스턴스를 가집니다. AspectJ에서는 이를 싱글톤 인스턴스화 모델이라고 부릅니다. 하지만 다른 생명 주기를 가진 aspect를 정의하는 것도 가능합니다. Spring은 AspectJ의 perthis, pertarget, 및 pertypewithin 인스턴스화 모델을 지원하며, percflowpercflowbelow는 현재 지원하지 않습니다.

@Aspect 애노테이션에서 perthis 절을 지정하여 perthis aspect를 선언할 수 있습니다. 다음은 그 예입니다:

@Aspect("perthis(execution(* com.xyz..service.*.*(..)))")
public class MyAspect {

    private int someState;

    @Before("execution(* com.xyz..service.*.*(..))")
    public void recordServiceUsage() {
        // ...
    }
}

위 예제에서 perthis 절의 효과는 비즈니스 서비스를 수행하는 각 고유한 서비스 객체(포인트컷 표현식에 의해 매칭되는 조인 포인트에서 this에 바인딩된 고유 객체)마다 하나의 aspect 인스턴스가 생성된다는 것입니다. 서비스 객체의 메서드가 처음 호출될 때 aspect 인스턴스가 생성됩니다. 서비스 객체가 스코프에서 벗어나면 aspect도 스코프에서 벗어납니다. aspect 인스턴스가 생성되기 전에는 그 안에 선언된 advice는 실행되지 않습니다. aspect 인스턴스가 생성되면, 해당 인스턴스와 연결된 서비스 객체가 참여하는 매칭된 조인 포인트에서 선언된 advice가 실행됩니다. per 절에 대한 더 자세한 내용은 AspectJ 프로그래밍 가이드를 참조하세요.

pertarget 인스턴스화 모델은 perthis와 동일한 방식으로 작동하지만, 매칭된 조인 포인트에서 각 고유한 타겟 객체마다 하나의 aspect 인스턴스를 생성합니다.

An AOP Example

지금까지 구성 요소들이 어떻게 작동하는지 보았으므로, 이제 이들을 조합하여 유용한 작업을 수행하는 방법을 알아보겠습니다.

비즈니스 서비스의 실행은 때때로 동시성 문제(예: 교착 상태에서 패배)가 원인이 되어 실패할 수 있습니다. 만약 작업을 다시 시도하면, 다음 시도에서 성공할 가능성이 높습니다. 이러한 상황에서 다시 시도하는 것이 적절한 비즈니스 서비스(사용자에게 충돌 해결을 위해 다시 돌아갈 필요가 없는 멱등 연산)에서는 클라이언트가 PessimisticLockingFailureException을 보지 않도록 작업을 투명하게 재시도하고 싶습니다. 이러한 요구사항은 서비스 계층의 여러 서비스에 걸쳐 발생하므로, aspect로 구현하기에 이상적입니다.

작업을 다시 시도하려면, 여러 번 proceed를 호출할 수 있도록 around advice를 사용해야 합니다. 다음은 기본적인 aspect 구현을 보여줍니다:

@Aspect
public class ConcurrentOperationExecutor implements Ordered {

    private static final int DEFAULT_MAX_RETRIES = 2;

    private int maxRetries = DEFAULT_MAX_RETRIES;
    private int order = 1;

    public void setMaxRetries(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    @Around("com.xyz.CommonPointcuts.businessService()") 
    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
        int numAttempts = 0;
        PessimisticLockingFailureException lockFailureException;
        do {
            numAttempts++;
            try {
                return pjp.proceed();
            }
            catch(PessimisticLockingFailureException ex) {
                lockFailureException = ex;
            }
        } while(numAttempts <= this.maxRetries);
        throw lockFailureException;
    }
}

여기서 businessService라는 이름의 포인트컷을 참조하는 부분은 Named Pointcut 정의에서 가져온 것입니다.

이 aspect는 Ordered 인터페이스를 구현하여, 트랜잭션 어드바이스보다 높은 우선순위를 가지도록 설정할 수 있습니다(재시도할 때마다 새로운 트랜잭션을 원하기 때문입니다). maxRetriesorder 속성은 Spring에 의해 구성됩니다. 주요 동작은 doConcurrentOperation around advice에서 발생합니다. 현재는 모든 businessService에 재시도 로직을 적용합니다. 먼저 proceed를 시도하고, PessimisticLockingFailureException이 발생하면 재시도합니다. 단, 모든 재시도 기회를 다 소진했을 경우에는 예외를 다시 던집니다.

다음은 해당하는 Spring 설정입니다:

<aop:aspectj-autoproxy/>

<bean id="concurrentOperationExecutor"
        class="com.xyz.service.impl.ConcurrentOperationExecutor">
    <property name="maxRetries" value="3"/>
    <property name="order" value="100"/>
</bean>

이 aspect를 멱등 연산에만 재시도하도록 개선하기 위해, 다음과 같은 Idempotent 애노테이션을 정의할 수 있습니다:

@Retention(RetentionPolicy.RUNTIME)
// 마커 애노테이션
public @interface Idempotent {
}

그런 다음 이 애노테이션을 서비스 연산의 구현에 적용할 수 있습니다. 멱등 연산에만 재시도하도록 aspect를 변경하려면 포인트컷 표현식을 수정하여 @Idempotent 애노테이션이 적용된 연산만 매칭되도록 해야 합니다. 다음은 그 예입니다:

@Around("execution(* com.xyz..service.*.*(..)) && " +
        "@annotation(com.xyz.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
    // ...
}

 

'Spring Framework > Aspect Oriented Programming with Spring' 카테고리의 다른 글

Using AspectJ with Spring Applications  (0) 2023.05.26
LockMin  (0) 2023.05.24
Declaring Advice  (0) 2023.05.23
Spring instrument library  (0) 2023.05.22
@AspectJ support  (0) 2023.05.22