예제 만들기
지금까지 학습한 내용을 활용하여 유용한 스프링 AOP를 만들어보자.
• @Trace 어노테이션으로 로그 출력하기
• @Retry 어노테이션으로 예외 발생 시 재시도 하기
먼저 AOP를 적용할 예제를 만든다.
간단한 Service와 Repository를 만드는데, Repository에서 5번의 요청이 올 때마다 의도적으로 예외를 터트린다.
@RequiredArgsConstructor
@Service
public class ExamService {
private final ExamRepository repository;
public void save(String itemId){
repository.save(itemId);
}
}
@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";
}
}
예외가 잘 터지는지 테스트 해본다.
@Slf4j
@SpringBootTest
public class ExamTest {
@Autowired ExamService examService;
@Test
void test() {
for (int i = 0; i < 5; i++) {
log.info("client request num = {}", i);
examService.save("data" + i);
}
}
}
실행결과:
client request num = 0
client request num = 1
client request num = 2
client request num = 3
client request num = 4
예외 발생
java.lang.IllegalStateException: 예외 발생
at hello.aop.exam.ExamRepository.save(ExamRepository.java:13)
....
이제 로그를 출력하는 AOP를 만들어보자.
먼저 @Trace라는 어노테이션으 붙은 메서드에 로그를 적용할 것이기 때문에, 아래와 같이 @Trace어노테이션을 만들어준다.
@Retention(RUNTIME)
@Target(METHOD)
public @interface Trace {
}
그리고 이를 적용한 로그 출력 Aspect를 만든다.
@Slf4j
@Aspect
public class TraceAspect {
@Before("@annotation(hello.aop.exam.annotation.Trace)")//Trace어노테이션
public void doTrace(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
log.info("[trace] {} args={}",joinPoint.getSignature(), args);
}
}
해당 로그 AOP는 메서드로 넘어온 인자도 출력한다.
그리고 이제 ExamService와 ExamRepository의 메서드에 @Trace어노테이션을 붙여준다.
@RequiredArgsConstructor
@Service
public class ExamService {
private final ExamRepository repository;
@Trace
public void save(String itemId){
repository.save(itemId);
}
}
@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";
}
}
이를 테스트해보자.
@Slf4j
@Import(TraceAspect.class)
@SpringBootTest
public class ExamTest {
@Autowired ExamService examService;
@Test
void test() {
for (int i = 0; i < 5; i++) {
log.info("client request num = {}", i);
examService.save("data" + i);
}
}
}
실행결과:
client request num = 0
[trace] void hello.aop.exam.ExamService.save(String) args=[data0]
[trace] String hello.aop.exam.ExamRepository.save(String) args=[data0]
client request num = 1
[trace] void hello.aop.exam.ExamService.save(String) args=[data1]
[trace] String hello.aop.exam.ExamRepository.save(String) args=[data1]
client request num = 2
[trace] void hello.aop.exam.ExamService.save(String) args=[data2]
[trace] String hello.aop.exam.ExamRepository.save(String) args=[data2]
client request num = 3
[trace] void hello.aop.exam.ExamService.save(String) args=[data3]
[trace] String hello.aop.exam.ExamRepository.save(String) args=[data3]
client request num = 4
[trace] void hello.aop.exam.ExamService.save(String) args=[data4]
[trace] String hello.aop.exam.ExamRepository.save(String) args=[data4]
예외 발생
java.lang.IllegalStateException: 예외 발생
로그가 잘 출력되는 것을 볼 수 있다.
이번에는 리퍼지토리에서 5번째 시도에서 예외가 터지는 경우에 재시도를 하는 AOP를 만들어보자.
먼저 @Retry어노테이션을 만든다.
@Retention(RUNTIME)
@Target(METHOD)
public @interface Retry {
int value() default 3; //기본값을 3으로 설정
}
이제 Aspect를 보자.
@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 retryCnt=1; retryCnt <= maxRetry; retryCnt++){
try {
log.info("[retry] retry Count = {}/{}",retryCnt, maxRetry);
return joinPoint.proceed();//여러번 호출가능
} catch (Exception e) {
exceptionHolder = e;
e.printStackTrace();
}
}
throw exceptionHolder;
}
}
위의 Aspect는 예외가 터졌을 때, 재시도 하는 애스펙트이다.
• @annotation(retry) , Retry retry 를 사용해서 어드바이스에 애노테이션을 파라미터로 전달한다.
• retry.value() 를 통해서 애노테이션에 지정한 값을 가져올 수 있다.
• 예외가 발생해서 결과가 정상 반환되지 않으면 retry.value() 만큼 재시도한다
이를 ExamRepository의 메서드에 적용해보자.
@Repository
public class ExamRepository {
private static int seq = 0;
/*
* 5번에 1번 실패하는 요청
* */
@Trace
@Retry(4) //4번 시도하도록 변경
public String save(String itemId){
seq++;
if(seq % 5 == 0) throw new IllegalStateException("예외 발생");
return "ok";
}
}
이를 테스트해보자.
@Slf4j
@Import({TraceAspect.class, RetryAspect.class})
@SpringBootTest
public class ExamTest {
@Autowired ExamService examService;
@Test
void test() {
for (int i = 0; i < 5; i++) {
log.info("client request num = {}", i);
examService.save("data" + i);
}
}
}
실행결과:
client request num = 0
[trace] void hello.aop.exam.ExamService.save(String) args=[data0]
[trace] String hello.aop.exam.ExamRepository.save(String) args=[data0]
[retry] String hello.aop.exam.ExamRepository.save(String) retry=@hello.aop.exam.annotation.Retry(4)
[retry] retry Count = 1/4
client request num = 1
[trace] void hello.aop.exam.ExamService.save(String) args=[data1]
[trace] String hello.aop.exam.ExamRepository.save(String) args=[data1]
[retry] String hello.aop.exam.ExamRepository.save(String) retry=@hello.aop.exam.annotation.Retry(4)
[retry] retry Count = 1/4
client request num = 2
[trace] void hello.aop.exam.ExamService.save(String) args=[data2]
[trace] String hello.aop.exam.ExamRepository.save(String) args=[data2]
[retry] String hello.aop.exam.ExamRepository.save(String) retry=@hello.aop.exam.annotation.Retry(4)
[retry] retry Count = 1/4
client request num = 3
[trace] void hello.aop.exam.ExamService.save(String) args=[data3]
[trace] String hello.aop.exam.ExamRepository.save(String) args=[data3]
[retry] String hello.aop.exam.ExamRepository.save(String) retry=@hello.aop.exam.annotation.Retry(4)
[retry] retry Count = 1/4
client request num = 4
[trace] void hello.aop.exam.ExamService.save(String) args=[data4]
[trace] String hello.aop.exam.ExamRepository.save(String) args=[data4]
[retry] String hello.aop.exam.ExamRepository.save(String) retry=@hello.aop.exam.annotation.Retry(4)
[retry] retry Count = 1/4
[retry] retry Count = 2/4 // retry해서 성공한 것을 알 수 있다.
김영한님의 인프런 강의와 PDF를 바탕으로 정리하였습니다.
'Spring > spring AOP' 카테고리의 다른 글
스프링 코어 2 - 프록시 기술과 한계 (0) | 2022.01.19 |
---|---|
스프링 코어 2 - 주의사항1 (내부 호출 문제와 해결) (0) | 2022.01.17 |
스프링 코어 2 - 포인트컷-매개변수 전달, this와 target (0) | 2022.01.16 |
스프링 코어 2 - 포인트컷 (0) | 2022.01.14 |
스프링 코어 2 - 어드바이스 종류 (0) | 2022.01.14 |