개발놀이터
스프링 AOP - 실전 예제 본문
이 포스팅은 인프런 김영한 님의 스프링 핵심 원리 고급 편을 보고 각색한 포스팅입니다. 자세한 내용은 강의를 확인해주세요
지금까지 학습한 내용을 활용해서 유용한 스프링 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
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
이제 예외가 터졌을 때 재시작하는 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 |