개발놀이터

스프링 AOP - 실전 예제 본문

Spring/Spring

스프링 AOP - 실전 예제

마늘냄새폴폴 2022. 1. 24. 19:18

이 포스팅은 인프런 김영한 님의 스프링 핵심 원리 고급 편을 보고 각색한 포스팅입니다. 자세한 내용은 강의를 확인해주세요

 

 

지금까지 학습한 내용을 활용해서 유용한 스프링 AOP를 만들어보자

 

1. @Trace 어노테이션으로 로그 출력하기

2. @Retry 어노테이션으로 예외 발생시 재시도 하기

 

cf) 어? 예외 발생시 재시도 하는 로직이 왜 필요하지? 예를 들어서 API통신을 하는데 조회를 해야한다고 가정해보자 특정 시간대 혹은 특정 횟수에 한번꼴로 예외가 터지는 경우라고 생각했을 때 조회라면 예외를 무시하고 다시 재시도 하면 되는 경우가 많다. 때문에 예외 로그가 찍히는 상황을 방지하고자 조회로직에 한해서 예외 발생시 재시도 하는 로직을 만드는 것이다.

 

먼저 어노테이션을 만들어야한다. 

 

Trace.java

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

Retry.java

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

 

이제 어드바이저를 만들어보자 이전에 배운 @Aspect, @Around, @Before를 이용하여 만들 것이다. 해당 내용이 기억나지 않는다면 아래의 포스팅을 참고하자

https://coding-review.tistory.com/99

 

@Aspect

이 포스팅은 인프런 김영한 님의 스프링 핵심 원리 고급 편을 보고 각색한 포스팅입니다. 자세한 내용은 강의를 확인해주세요 스프링은 @Aspect 어노테이션으로 매우 편리하게 포인트컷과 어드바

coding-review.tistory.com

 

TraceAspect.java

@Slf4j
@Aspect
public class TraceAspect {

    @Before("@annotation(hello.review.exam.annotation.Trace)")
    public void doTrace(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        log.info("[trace] {} args={}", joinPoint.getSignature(), args);
    }
}

클래스레벨에 @Aspect를 적어 이 클래스가 AOP의 대상임을 명시한다. @Before를 사용해 이 어드바이스가 메서드 호출 전에 로그를 찍도록 한다. 또한 @annotation을 사용해 포인트컷을 설정해준다. 그리고 로그로 메서드정보(시그니처)와 인자값을 찍는다. 

 

@annotation에 대해 아직 잘 모르겠다면 아래의 포스팅을 참고하자

https://coding-review.tistory.com/100

 

스프링 AOP - 포인트컷

이 포스팅은 인프런 김영한 님의 스프링 핵심 원리 고급 편을 보고 각색한 포스팅입니다. 자세한 내용은 강의를 확인해주세요 지금부터 포인트컷 표현식을 포함한 포인트컷에 대해서 알아보자

coding-review.tistory.com

 

이제 예외가 터졌을 때 재시작하는 RetryAspect를 만들어보자

RetryAspect.java

@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로 해당 클래스가 AOP의 대상이 되게 설정한 후에 @Around를 사용했다.

 

@Around와 @Before나 @After등의 차이는 @Around는 joinPoint.proceed()를 자유자재로 사용할 수 있다는 점이 가장 큰 차이점이다. 단순히 메서드 호출 전/후에 호출하는 것이 아니라 로직이 정해져있고 proceed를 자유롭게 설정해야 한다면 @Around를 사용하자

 

또한 @annotation을 파라미터 바인딩을 통해 어노테이션의 값을 사용할 수 있게 했다. 

 

또한 exceptionHolder를 만들어서 예외가 발생되더라도 예외가 for문이 다 돌기 전까지 터지지않고 붙잡아 놓을 수 있게 해놓았다. 

 

Retry의 디폴트 값으로 설정된 3의 값을 꺼내 for문에 반복 횟수로 넣어주었다. 이렇게 하지 않으면 계속 요청이 들어가 셀프 디도스를 만들 수 있다. 

 

이제 위의 어노테이션과 AOP를 활용해 비즈니스 로직을 만들어보자

 

ExamRepository.java

@Repository
public class ExamRepository {

    private static int seq = 0;

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

sequence를 설정해 놓고 다섯번에 한번씩 예외가 터진다고 가정한 코드이다. 메서드에 우리가 만든 @Trace, @Retry를 달아 놓았다. 또한 Retry의 디폴트 값을 4로 변경해주었다. 

 

ExamService.java

@Service
@RequiredArgsConstructor
public class ExamService {

    private final ExamRepository examRepository;

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

Service는 단순히 Repository를 호출하는 역할만을 한다. 여기에는 @Trace를 달아주었다.

 

이제 테스트코드로 예외가 발생했을 때 재시도가 되는지, 로그가 제대로 남는지 확인해보자

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

    @Autowired
    ExamService examService;

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

실행해보면 다음과 같이 나온다. 

trace로그가 잘 찍히고 retry도 잘 작동되는 것을 확인할 수 있다. 다섯번에 한번씩 예외가 터지기 때문에 첫번째, 두번째, 세번째, 네번째에는 retry횟수가 한번이지만 마지막 다섯번째에 retry를 한번 더 했기 때문에 try count가 2로 늘어난 것을 확인할 수 있다. 

 

 

'Spring > Spring' 카테고리의 다른 글

커넥션 풀 (Connection pool)  (0) 2022.06.21
@Target, @Retention  (0) 2022.01.24
스프링 AOP - 포인트컷  (0) 2022.01.24
@Aspect  (0) 2022.01.20
빈 후처리기  (0) 2022.01.20