BackEnd/Spring

[Spring] 스프링 AOP - 실전 예제

샤아이인 2022. 8. 20.

내가 공부한것을 올리며, 중요한 단원은 저 자신도 곱씹어 볼겸 상세히 기록하고 얕은부분들은 가겹게 포스팅 하겠습니다.

[Spring] 스프링 AOP - 실전 예제

 

1. 예제 만들기

이번 시간에는 예제 준비코드를 작성해보자.

 

▶ ExamRepository

@Repository
public class ExamRepository {

    private static int seq = 0;

    /**
     * 5번에 한번 실패하는 요청
     */
    public String save(String itemId) {
        seq++;
        if (seq % 5 == 0) {
            throw new IllegalStateException("예외 발생");
        }
        return "ok";
    }
}

 

▶ ExamService

@Service
@RequiredArgsConstructor
public class ExamService {

    private final ExamRepository examRepository;

    public void request(String itemId) {
        examRepository.save(itemId);
    }
}

 

▶ ExamTest

@Slf4j
@SpringBootTest
public class ExamTest {

    @Autowired
    ExamService examService;
    
    @Test
    public void test() {
        for (int i = 0; i < 5; i++) {
            examService.request("data" + i);
        }
    }
}

 

2. 로그 출력 AOP

먼저 로그 출력용 AOP를 만들어보자.

@Trace 가 메서드에 붙어 있으면 호출 정보가 출력되는 편리한 기능이다.

 

▶ Trace

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Trace {}

 

▶ TraceAspect

@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 가 붙은 메서드에 어드바이스를 적용할 것 이다.

부가기능으로 파라미터로 넘어온 인자들을 출력해줄 것 이다.

 

이후 ExamService#request 와 ExamRepository#save 에 @Trace 를 붙였다.

 

테스트 코드를 통해서 확인해보자.

@Slf4j
@Import(TraceAspect.class)
@SpringBootTest
public class ExamTest {

    @Autowired
    ExamService examService;
    
    @Test
    public void test() {
        for (int i = 0; i < 5; i++) {
            log.info("client request i={}", i);
            examService.request("data" + i);
        }
    }
}

일단 @Import를 통해서 빈을 등록시켰다.

 

실행 결과는 다음과 같다.

[Spring] 스프링 AOP - 실전 예제 - 				
    
    	2. 로그 출력 AOP

실행시 @Trace 가 붙은 request() , save() 호출시 로그가 잘 남는 것을 확인할 수 있다.

 

3. 재시도 AOP

이번에는 재시도 AOP를 만들것 이다.

 

이전의 우리 코드는 5번에 한번은 예외를 던지도록 만들었었다. 따라서 예외가 발생하면 retry하여 요청을 완수해야 한다.

@Retry 애노테이션이 있으면 예외가 발생했을 때 다시 시도해서 문제를 복구하도록 구현해보자.

 

▶ Retry

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Retry {
    int value() default 3;
}

 

▶ RetryAspect

@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;
    }
}

요청 실패시 재시도를 하는 Aspect 이다.

 

코드를 보면 @annotation(retry) , Retry retry 를 사용해서 어드바이스에 애노테이션을 파라미터로 전달한다.

해당 retry 안에 value에 몇번 재시도 할지 담겨져있다.

예외가 발생해서 결과가 정상 반환되지 않으면 retry.value() 만큼 재시도하는 것 이다.

 

다음과 같이 @Retry 를 적용시켜보자.

@Repository
public class ExamRepository {
      
    @Trace
    @Retry(value = 4)
    public String save(String itemId) {
        //...
    } 
}

ExamRepository.save() 메서드에 @Retry(value = 4) 를 적용했다. 이 메서드에서 문제가 발생하면 4번 재시도 한다.

 

@Import({TraceAspect.class, RetryAspect.class}) 를 스프링 빈으로 추가하자.

위에서 봤던 테스트코드를 실행하면 이번에는 정상적으로 수행하고 끝나게 된다.

 

출력한 로그는 다음과 같다.

[Spring] 스프링 AOP - 실전 예제 - 				
    
    	3. 재시도 AOP

마지막 5번째 시도에서 첫번째 try에서 예외가 발생하여 2번째 try를 한다.

이때 성공하여 끝나게 된다. 재시도 덕분에 문제가 복구되고, 정상 응답되는 것을 확인할 수 있다.

댓글