
로그 AOP
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Trace {
}
@Slf4j
@Aspect
public class TraceAspect {
@Before("@annotation(hello.aop.exam.annotation.Trace)")
public void doTrace(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
log.info("[trace] {} args={}", joinPoint.getSignature(), args);
}
}
재시도 AOP
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Retry {
int value() default 3;
}
@Slf4j
@Aspect
public class RetryAspect {
@Around("@annotation(retry)")
public Object doRetry(ProceedingJoinPoint joinPoint, Retry retry) throws
Throwable {
log.info("[retry] {} retry={}", joinPoint.getSignature(), retry);
int maxRetry = retry.value();
Exception exceptionHolder = null;
for (int retryCount = 1; retryCount <= maxRetry; retryCount++) {
try {
log.info("[retry] try count={}/{}", retryCount, maxRetry);
return joinPoint.proceed();
} catch (Exception e) {
exceptionHolder = e;
}
}
throw exceptionHolder;
}
}
리포지토리, 서비스
@Repository
public class ExamRepository {
private static int seq = 0;
@Trace
public String save(String itemId) {
seq++;
if (seq % 5 == 0) {
throw new IllegalStateException("예외 발생");
}
return "ok";
}
}
@Service
@RequiredArgsConstructor
public class ExamService {
private final ExamRepository examRepository;
@Trace
@Retry(value = 4)
public void request(String itemId) {
examRepository.save(itemId);
}
}
TEST
@SpringBootTest
@Import({TraceAspect.class, RetryAspect.class})
public class ExamTest {
@Autowired
ExamService examService;
@Test
void test() {
for (int i = 0; i < 5; i++) {
examService.request("data" + i);
}
}
}
결과
- 5의 배수마다 예외를 던지는 테스트
- 일단 모든 메소드 실행 시 [Trace] 로그가 찍히도록 AOP 지정
- 만약 5의 배수가 나와 예외가 발생한 경우 재시도를 한다
- 예)
- 0, 1, 2, 3 까지는 정상작동
- 4 부터 seq=5 이므로 예외 발생
- 재시도 AOP에서 예외를 잡은 후 재시도
- 재시도 시 seq=6 이므로 예외가 발생하지 않고 정상 작동

