Spring AOP를 이용하여 특정상황에 AOP를 적용할 수 있습니다.
이번 포스팅에서 설명할 내용은 다음과 같습니다.
----------------------------------------------------------
- 직접 만든 @Trace 어노테이션이 붙은 메소드에 AOP 적용하기
- 예외가 발생했을 경우 재시도를 하는 AOP 적용하기
- 메소드의 실행시간이 일정 시간을 초과했을 경우 AOP 적용하기
@Trace 어노테이션이 붙은 메소드에 AOP 적용
먼저 직접 만든 @Trace 어노테이션이 붙은 메소드에 AOP 적용하는 방법을 알아 보겠습니다.
1. @Trace 어노테이션을 생성합니다.
@Trace 어노테이션
package hello.aop.exam.annotation;
...
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Trace {
}
2. @Trace 어노테이션이 붙은 메서드에 로그를 출력하는 Advisor를 만듭니다.
TraceAspect
package hello.aop.exam.aop;
...
@Slf4j
@Aspect
public class TraceAspect {
@Before("@annotation(hello.aop.exam.annotation.Trace)")
public void doTrace(JoinPoint joinPoint) {
log.info("[trace] {} args = {}", joinPoint.getSignature(), joinPoint.getArgs());
}
}
3. AOP를 적용할 Repository, Service를 생성합니다.
ExamRepository
데이터를 저장시 (save) seq 값(ID)이 5번 째가 될 때마다 예외가 발생합니다.
save() 메서드 실행 시 로그를 적용하기 위해 @Trace 어노테이션을 붙여줍니다.
package hello.aop.exam;
...
@Repository
public class ExamRepository {
private static int seq = 0;
/**
* 5번에 1번 실패하는 요청
*/
@Trace
public String save(String itemId) {
seq++;
if (seq % 5 == 0) {
throw new IllegalStateException("예외 발생");
}
return "ok";
}
}
ExamService
클라이언트의 요청을 받아 요청 값을 ExamRepository에 저장합니다.
request() 메서드 실행 시 로그를 적용하기 위해 @Trace 어노테이션을 붙여줍니다.
package hello.aop.exam;
...
@Service
@RequiredArgsConstructor
public class ExamService {
private final ExamRepository examRepository;
@Trace
public void request(String itemId) {
examRepository.save(itemId);
}
}
4. 이제 테스트 코드로 @Trace 어노테이션이 붙은 메서드에 로그 출력 기능이 잘 적용되는지 확인해봅시다.
@Slf4j
@Import(TraceAspect.class)
@SpringBootTest
public class ExamTest {
@Autowired
ExamService examService;
@Test
void test() {
for (int i = 0; i < 5; i++) {
log.info("client request : {} 번째 요청", i);
examService.request("data" + i);
}
}
}
로그를 확인해 보면 TraceAspect가 잘 적용 되어, @Trace 어노테이션이 붙은 메서드의 로그 정보가 찍히는 것을 볼 수 있습니다.
그런데 여기서 5번 째 요청에 예외가 발생합니다.
바로 ExamRepository에서 데이터를 저장시 (save) seq 값(ID)이 5번 째가 될 때마다 예외가 발생하도록 설정했기 때문인데요.
재시도 AOP를 적용하여 이 부분을 해결 해 봅시다.
예외가 발생했을 경우 재시도 AOP 적용
재시도 AOP를 적용하는 경우는 예를 들어서
"서버와 서버가 통신을 할 때, 어쩌다 한번씩 오류가 발생해서 다시 요청하면 성공할 경우"에 사용할 수 있습니다.
이제 재시도를 적용할 @Retry 어노테이션을 만들어서 적용 해 보겠습니다.
1. @Retry 어노테이션 생성합니다.
@Retry 어노테이션
재시도 AOP를 적용할 때는 주의할 점이 있습니다.
바로 재시도 횟수를 꼭 제한 해줘야 합니다. (여기서는 재시도 횟수를 기본 값 3으로 설정하였습니다.)
package hello.aop.exam.annotation;
...
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Retry {
// 재시도 AOP를 설정할 때는 꼭 재시도 횟수를 제한해야 한다.
int value() default 3; // 기본값 3
}
2. @Retry어노테이션이 붙은 메서드에 예외가 발생했을 경우 재시도하는 Advisor를 만듭니다.
RetryAspect
- @annotation(retry) , Retry retry 를 사용해서 어드바이스에 애노테이션을 파라미터로 전달
- retry.value() 를 통해서 애노테이션에 지정한 값을 가져온다. (@Retry의 기본 값이 3이므로 maxRetry = 3)
- 지정한 재시도 횟수 동안 재시도를 반복하며, 지정한 횟수에 도달 하기전에 예외가 발생하지 않으면 proceed()로 타겟을 호출
- 지정한 재시도 횟수까지 예외가 발생한다면 발생한 예외를 출력
package hello.aop.exam.aop;
...
@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;
}
}
3. 예외가 터지는 ExamRespository의 save() 메서드에 재시도를 위해 @Retry 어노테이션을 붙여줍니다.
// 위 ExamRepository 코드와 같음
@Trace
@Retry
public String save(String itemId) {
...
}
4. 이제 테스트 코드로 예외 발생 시 @Retry 어노테이션이 붙은 메서드에 재시도 기능이 잘 작동하는지 확인해봅시다.
package hello.aop.exam;
...
@Slf4j
@Import({TraceAspect.class, RetryAspect.class})
@SpringBootTest
public class ExamTest {
@Autowired
ExamService examService;
@Test
void test() {
for (int i = 0; i < 5; i++) {
log.info("client request : {} 번째 요청", i);
examService.request("data" + i);
}
}
}
4번 째 요청 까지 문제 없이 잘 작동하다가 5번 째 요청에서 예외가 발생하여 재시도 기능이 작동해 2번의 재시도 만에 정상 작동한 것을 확인할 수 있습니다.
특정시간 이상 실행 시 시간초과 알림 AOP 적용
이번에는 메서드의 실행 시간이 일정 시간을 초과했을 경우, 기준 실행 시간을 초과했다고 로그를 찍어주는 시간초과 AOP를 적용해보겠습니다.
1. @RunningTimeCheck 어노테이션 생성합니다.
기본값을 1000으로 세팅해, 1000초 안으로 실행되도록 하였습니다.
@RunningTimeCheck
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RunningTimeCheck {
int value() default 1000; // 기본 10초
}
2. @RunningTimeCheck 어노테이션이 붙은 메서드에서 시간초과가 발생했을 경우 알림을 보내는 Advisor를 만듭니다.
- @annotation(timeCheck) , RunningTimeCheck timeCheck를 사용해서 어드바이스에 애노테이션을 파라미터로 전달
- retry.value() 를 통해서 애노테이션에 지정한 값을 가져온다. (@RunningTimeCheck 의 기본 값이 1000이므로 millis = 1000)
- 편리한 시간측정을 위해 스프링의 StopWatch 클래스를 사용하였습니다.
- 실행 시간이 기준 시간 (1000)을 넘지 않는다면 [정상 실행] 로그 출력
- 실행 시간이 기준 시간 (1000)을 넘는 다면 [!경고] 로그 출력
TimeCheckAspect
package hello.aop.exam.aop;
...
@Slf4j
@Aspect
public class TimeCheckAspect {
@Around("@annotation(timeCheck)")
public void checkTimer(ProceedingJoinPoint joinPoint, RunningTimeCheck timeCheck) throws Throwable {
int mills = timeCheck.value(); // 기본 설정 1초
StopWatch stopWatch = new StopWatch(); // 스프링의 StopWatch 클래스 사용
stopWatch.start();
joinPoint.proceed();
stopWatch.stop();
long runningTime = stopWatch.getTotalTimeMillis();
String methodName = joinPoint.getSignature().getName();
if (runningTime <= mills) {
log.info("[정상 실행] method = {}, 실행시간 = {} ms", methodName, runningTime);
} else {
log.error("[!경고] [기준 실행 시간을 초과하였습니다] method = {}, 실행시간 = {} ms", methodName, runningTime);
}
}
}
3. [!경고] 알림을 받을 수 있도록 ExamRepository의 save() 메서드에 @RunningTimeCheck 어노테이션을 붙이고 실행시간을 3초가 걸리도록 세팅합니다.
ExamRepository
@Repository
public class ExamRepository {
private static int seq = 0;
@RunningTimeCheck
public String save(String itemId) {
sleep(3000);
return "ok";
}
private void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4. 이제 테스트 코드로 예외 발생 시 @Retry 어노테이션이 붙은 메서드에 재시도 기능이 잘 작동하는지 확인해봅시다.
@Slf4j
@Import(TimeCheckAspect.class)
@SpringBootTest
public class ExamTest {
@Test
void test() {
log.info("--- client request --- ");
examService.request("data");
}
}
examService는 클라이언트의 요청을 받아 요청값을 ExamRepository의 save()로 값을 저장하는데
save()의 실행시간이 3초기 때문에 기준 시간 1초(1000)를 초과해 다음과 같이 경고 로그를 출력하는 것을 볼 수 있습니다.
참고자료 : 김영한의 스프링 핵심 원리 - 고급편
'◼ Spring' 카테고리의 다른 글
[Spring] 배포 환경 별로 설정파일 분리하기 (프로필) (0) | 2023.05.19 |
---|---|
[Spring] 스프링 AOP 주의사항 - 프록시 내부호출 (0) | 2023.05.17 |
[Spring] 스프링 AOP - Pointcut 표현식 (2) | 2023.05.16 |
[Spring] 스프링 AOP (@Aspect) 사용법 (0) | 2023.05.15 |
[Spring] 스프링 AOP(Aspect Oriented Programming)란? - @Aspect (0) | 2023.05.12 |