스프링 핵심 원리 - 고급편 - (34) 스프링 AOP - 실전 예제
인프런 스프링 핵심 원리 - 고급편을 학습하고 정리한 내용 입니다.
예제 만들기
지금까지 학습한 내용을 활용해서 유용한 스프링 AOP를 만들어보자.
@Trace애노테이션으로 로그 출력하기@Retry애노테이션으로 예외 발생시 재시도 하기
먼저 AOP를 적용할 예제를 만들자.

해당 위치에 예제 리포지토리를 하나 만든다.
ExamRepository
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Repository
public class ExamRepository {
private static int seq = 0;
/**
* 5번에 1번 실패하는 요청
* */
public String save(String itemId) {
seq++;
if (seq % 5 == 0) {
throw new IllegalStateException("예외 발생!");
}
return "ok";
}
}
5번에 1번 정도 실패하는 저장소이다. 이렇게 간헐적으로 실패할 경우 재시도 하는 AOP가 있으면 편리하다.
ExamService
1
2
3
4
5
6
7
8
9
10
@RequiredArgsConstructor
@Service
public class ExamService {
private final ExamRepository examRepository;
public void request(String itemId) {
examRepository.save(itemId);
}
}
실행시키는 서비스를 만들었다.
이제 테스트 코드를 작성하자.

ExamTest
1
2
3
4
5
6
7
8
9
10
11
12
13
@SpringBootTest
public class ExamTest {
@Autowired
ExamService examService;
@Test
void test() {
for (int i = 0; i < 5; i++) {
examService.request("data" + i);
}
}
}
실행해보면 테스트가 5번째 루프를 실행할 때 리포지토리 위치에서 예외가 발생하면서 실패하는 것을 확인할 수 있다.

로그 출력 AOP
먼저 로그 출력용 AOP를 만들어보자.
@Trace가 메서드에 붙어 있으면 호출 정보가 출력되는 편리한 기능이다.

Trace
1
2
3
4
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Trace {
}
이 애노테이션을 감지할 TraceAspect를 만들자.
hello.aop.exam.aop.TraceAspect
1
2
3
4
5
6
7
8
9
10
@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);
}
}
@annotation(hello.aop.exam.annotation.Trace)포인트컷을 사용해서 @Trace가 붙은 메서드에 어드 바이스를 적용한다.
이제 각 메서드에 애노테이션을 붙혀보자.
1
2
3
4
5
6
7
8
9
10
11
@RequiredArgsConstructor
@Service
public class ExamService {
private final ExamRepository examRepository;
@Trace
public void request(String itemId) {
examRepository.save(itemId);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Repository
public class ExamRepository {
private static int seq = 0;
/**
* 5번에 1번 실패하는 요청
* */
@Trace
public String save(String itemId) {
seq++;
if (seq % 5 == 0) {
throw new IllegalStateException("예외 발생!");
}
return "ok";
}
}
자 마지막으로 테스트 코드에 애스펙트를 임포트 하면 끝이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@SpringBootTest
@Import(TraceAspect.class)
public class ExamTest {
@Autowired
ExamService examService;
@Test
void test() {
for (int i = 0; i < 5; i++) {
examService.request("data" + i);
}
}
}

실행해보면 @Trace가 붙은 request(), save()호출 시 로그가 잘 남는 것을 확인할 수 있다.
재시도 AOP
이번에는 좀 더 의미 있는 재시도 AOP를 만들어보자.
@Retry 애노테이션이 있으면 예외가 발생했을 때 다시 시도해서 문제를 복구한다.
hello.aop.exam.annotation.Retry
1
2
3
4
5
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Retry {
int value() default 3;
}
이 애노테이션에는 재시도 횟수로 사용할 값이 있다. 기본값으로 3을 사용한다.
애스펙트를 만들자.
hello.aop.exam.aop.RetryAspect
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Slf4j
@Aspect
public class RetryAspect {
@Around("@annotation(retry)")
public Object doRetry(ProceedingJoinPoint joinPoint, Retry retry) throws Throwable {
log.info("[retry] {} retry={}", joinPoint.getSignature(), retry);
Exception exceptionHolder = null;
int maxRetry = retry.value();
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;
}
}
- 재시도 하는 애스펙트이다.
@annotation(retry),Retry retry를 사용해서 어드바이스에 애노테이션을 파라미터로 전달한다.retry.value()를 통해서 애노테이션에 지정한 값을 가져올 수 있다.- 예외가 발생해서 결과가 정상 반환 되지 않으면
retry.value()만큼 재시도한다.
ExamRepository에 적용해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Repository
public class ExamRepository {
private static int seq = 0;
/**
* 5번에 1번 실패하는 요청
* */
@Trace
@Retry(value = 4)
public String save(String itemId) {
seq++;
if (seq % 5 == 0) {
throw new IllegalStateException("예외 발생!");
}
return "ok";
}
}
ExamRepository.save()메서드에 @Retry(value = 4)를 적용했다. 이 메서드에서 문제가 발생하면 4번 재시도 한다.
이제 마지막으로 테스트코드에 임포트 해서 확인해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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번째 문제가 발생했을 때 재시도 덕분에 문제가 복구 되고, 정상 응답 되는 것을 확인할 수 있다.
참고 : 스프링이 제공하는
@Transactional은 가장 대표적인 AOP이다.
댓글남기기