- 스프링 핵심원리 고급편 예제만들기
- 스프링 핵심원리 고급편(2) 쓰레드 로컬(threadlocal)
- 스프링 핵심원리 고급편(3) 템플릿 메서드 패턴과 콜백 패턴
- 스프링 핵심원리 고급편 프록시 패턴과 데코레이터 패턴
- 스프링 핵심원리 고급편(2) 동적 프록시 기술
- 스프링 핵심원리 고급편 스프링이 지원하는 프록시
- 스프링 핵심원리 고급편(2) 빈 후처리기
- 스프링 핵심원리 고급편(3) @aspect aop
- 스프링 핵심원리 고급편(3) 스프링 aop 구현
- 스프링 핵심원리 고급편 스프링 aop 포인트컷
- 스프링 핵심원리 고급편 스프링 aop 실전 예제
- 스프링 핵심원리 고급편(2) 스프링 aop 실무 주의사항
스프링 핵심원리 고급편 스프링 aop 실전 예제
0. 예제 만들기
에러가 발생할 때 3번 정도 재호출하는 AOP 만들기
예제 코드
ExamRepository
package hello.aop.exam;
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";
}
}
- 5번 당 1번 에러가 나도록 구성
ExamService
package hello.aop.exam;
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);
}
}
ExamTest (test)
package hello.aop.exam;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@Slf4j
@SpringBootTest
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);
}
}
}
1. 로그 출력 AOP
로그를 출력해주는 @Trace 와 TraceAspect 를 만들어보자.
@Trace
package hello.aop.exam.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 {
}
TraceAspect
package hello.aop.exam.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(hello.aop.exam.annotation.Trace)")
public void doTrace(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
log.info("[trace] {} args={}", joinPoint.getSignature(), args);
}
}
@annotation(hello.aop.exam.annotation.Trace) 포인트컷을 사용해서 @Trace 가 붙은 메서드에 어드바이스를 적용한다.
ExamService, ExamRepository
서비스 및 리파지토리 메서드에 @Trace 를 붙여준다.
ExamTest - 추가
package hello.aop.exam;
import hello.aop.exam.annotation.Trace;
import hello.aop.exam.aop.TraceAspect;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
@Slf4j
@SpringBootTest
@Import(TraceAspect.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);
}
}
}
@Import(TraceAspect.class)
로 빈으로 추가해준다.
실행결과는 아래와 같다.
[trace] void hello.aop.exam.ExamService.request(String) args=[data0]
[trace] String hello.aop.exam.ExamRepository.save(String) args=[data0]
[trace] void hello.aop.exam.ExamService.request(String) args=[data1]
[trace] String hello.aop.exam.ExamRepository.save(String) args=[data1]
...
2. 재시도 AOP
@Retry
package hello.aop.exam.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;
}
- 기본 재시도 횟수는 3 이다.
RetryAspect
package hello.aop.exam.aop;
import hello.aop.exam.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 {
//매개 변수의 retry 에서 타입정보를 읽어서 `("@annotation(retry)")` 에 넣는다.
@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;
}
}
maxRetry
: 최대 재시도 횟수exceptionHolder
: 마지막 재시도 때의 Exception- for 반복문을 통해 계속
joinPoint.proceed();
을 반복
ExamRepository - @Retry 추가
package hello.aop.exam;
import hello.aop.exam.annotation.Retry;
import hello.aop.exam.annotation.Trace;
import org.springframework.stereotype.Repository;
@Repository
public class ExamRepository {
private static int seq = 0;
/**
* 5번에 1번 실패하는 요청
*/
@Trace
@Retry(4)
public String save(String itemId){
seq++;
if(seq % 5 == 0){
throw new IllegalStateException("예외 발생");
}
return "ok";
}
}
@Retry(4)
와 같이 value 값을 지정할 수 있다. 기본 횟수는 3 이다.
ExamTest - 추가
@SpringBootTest
@Import({TraceAspect.class, RetryAspect.class})
public class ExamTest {
}
@Import({TraceAspect.class, RetryAspect.class}) 를 스프링 빈으로 추가하자.
실행결과
...
[retry] try count=1/5
[retry] try count=2/5
실행 결과를 보면 5번째 문제가 발생했을 때 재시도 덕분에 문제가 복구되고, 정상 응답되는 것을 확인할 수 있다.
댓글남기기