2023. 5. 24. 14:12ㆍSpring Framework/Aspect Oriented Programming with Spring
Aspect Instantiation Models
이것은 고급 주제입니다. AOP를 처음 시작하는 경우, 나중에 이 내용을 안전하게 건너뛰어도 됩니다.
기본적으로 각 aspect는 애플리케이션 컨텍스트 내에서 단일 인스턴스를 가집니다. AspectJ에서는 이를 싱글톤 인스턴스화 모델이라고 부릅니다. 하지만 다른 생명 주기를 가진 aspect를 정의하는 것도 가능합니다. Spring은 AspectJ의 perthis
, pertarget
, 및 pertypewithin
인스턴스화 모델을 지원하며, percflow
및 percflowbelow
는 현재 지원하지 않습니다.
@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
인터페이스를 구현하여, 트랜잭션 어드바이스보다 높은 우선순위를 가지도록 설정할 수 있습니다(재시도할 때마다 새로운 트랜잭션을 원하기 때문입니다). maxRetries
와 order
속성은 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 |