본문 바로가기
Spring/spring AOP

스프링 코어 2 - 스프링 AOP 실전 예제

by 킹차니 2022. 1. 16.

예제 만들기

 

지금까지 학습한 내용을 활용하여 유용한 스프링 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를 바탕으로 정리하였습니다.