[고급편] 스프링 AOP - 실전 예제
포스트
취소

[고급편] 스프링 AOP - 실전 예제

예제 만들기

  • 지금까지 학습한 내용을 활용해서 유용한 스프링 AOP를 만들어보자.
    • @Trace 애노테이션으로 로그 출력하기
    • @Retry 애노테이션으로 예외 발생시 재시도 하기

리포지토리

package com.example.app;

import org.springframework.stereotype.Repository;

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

서비스

package com.example.app;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class ExamService {
    private final ExamRepository examRepository;

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

테스트 생성

  • 예외가 발생하도록 테스트를 작성해보자.
import com.example.Chapter3Application;
import com.example.app.ExamService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;

@SpringBootTest
@ContextConfiguration(classes = Chapter3Application.class)
public class ExamTest {
    @Autowired
    private ExamService examService;

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

로그 출력 AOP

애노테이션

package com.example.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

애스팩트

package com.example.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Slf4j
@Aspect
public class TraceAspect {
    @Before("@annotation(com.example.annotation.Trace)")
    public void doTrace(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        log.info("[trace] {} args={}", joinPoint.getSignature(), args);
    }
}

AOP 적용

  • ExamRepository의 save에 @Trace를 추가한다.
  • ExamService의 request에 @Trace를 추가한다.
  • ExamTest에 @Import(TraceAspect.class)를 추가한다.
  • 이제 ExamTest를 실행하면 로그가 잘 출력되는 것을 확인할 수 있다.

재시도 AOP

애노테이션

package com.example.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

애스팩트

  • 재시도 하는 애스펙트
  • @annotation(retry)와 Retry retry를 사용해서 어드바이스에 애노테이션을 파라미터로 전달한다.
  • retry.value()를 통해서 애노테이션에 지정한 값을 가져올 수 있다.
  • 예외가 발생해서 결과가 정상 반환되지 않으면 retry.value()만큼 재시도한다
package com.example.aop;

import com.example.annotation.Retry;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.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 retryCount = 1; retryCount <= maxRetry; retryCount++) {
            try {
                log.info("[retry] try count={}/{}", retryCount, maxRetry);
                return joinPoint.proceed();
            } catch (Exception e) {
                exceptionHolder = e;
            }
        }
        
        throw exceptionHolder;
    }
}

AOP 적용

  • ExamRepository의 save에 @Retry(value = 4)를 추가한다.
  • ExamTest에서 @Import(TraceAspect.class)@Import({TraceAspect.class, RetryAspect.class})로 변경한다.
  • 이제 ExamTest를 실행하면 5번째 문제가 발생했을 때 재시도 덕분에 문제가 복구되고, 정상 응답되는 것을 확인할 수 있다.

참고

  • 스프링이 제공하는 @Transactional은 가장 대표적인 AOP이다.

출처

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.