예제 만들기
- 지금까지 학습한 내용을 활용해서 유용한 스프링 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이다.
출처