🔥 TIL-Day 83 Spring AOPによる異常時の再試行


Exceptionが発生すると、Spring AOPを使用して、この方法を自動的に再起動するエージェントが実装される.
リクエストが再試行から除外される可能性がある場合は、サーバ側が指定した回数で再試行することはクライアントにとって良い方法です.
ソースコードに触れずに適用したい方法にのみ適用されます.
ソースコードではなく、非ビジネスロジックの再試行ロジックが追加されているため、@Retryが適切であり、프록시 패턴をSpringでより使いやすくするために프록시 패턴を使用します.

📌 依存項目の追加


[ Spring AOP ]
implementation 'org.springframework.boot:spring-boot-starter-aop'

📌 再試行ターゲットとなるテストクラスとメソッドの作成

Spring AOPrepositoryメソッドは、5回目のアクセスごとに例外が発生します.
@Repository
public class ExamRepository {

    private static int sequence = 0;

    // 5번째 요청마다 예외를 발생시킨다.
    public String save(String itemId) {
        ++sequence;
        if (sequence%5 == 0) {
            throw new IllegalStateException("예외 발생");
        }
        return "ok";
    }
}
本来は5番目のリクエストごとに1つの例外が返されますが、例外が発生した場合は、再試行することで例外を回避します.

📌 Annotationを作成してJoinPointをマーク


再試行コンサルティングは、saveが添付されている方法に適用される.
成功するまで無限の再試行が行われないことを確認し、属性は@Retryを含み、異なる方法で最大再試行回数を適用する.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Retry {

    int value() default 3;
}

📌 調査の作成

  • valueの前後では、異常などの他の作業を処理する必要があるため、JoinPointが使用される.
  • @Around言語テストの名前が指定されており、このタイプをパラメータとしているため、PointCutが指定されています.
    パラメータを受け入れない場合は、@annotation()にパラメータのパケットパスとタイプを含める必要があります.
  • @annotation(어노테이션_이름)言語テストから最大再試行回数を取得し、再試行回数に基づいてRetryを呼び出す.( JoinPoint );

  • 要求に異常が発生した場合、直ちに異常を投げ出すのではなく一時保存し、最大再試行回数を超えると一時保存の異常が発生します.
  • @Aspect
    @Slf4j
    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 retryCnt=1; retryCnt<=maxRetry; retryCnt++) { // 재시도
           	    if (retryCnt > 1) {
                    log.info("{} 번째 재시도", retryCnt-1);
                }
                try {
                    return joinPoint.proceed(); // target 호출시 예외가 없다면 그대로 반환
                } catch (Exception e) {
                    exceptionHolder = e; // 예외가 발생했다면 발생한 예외를 보관
                }
            }
            throw exceptionHolder; // 최대 재시도 횟수를 넘어선 경우 예외발생
        }
    }

    📌 適用前にテスト


    これは再試行のために説明書を貼る前のテストです.
    @Slf4j
    @SpringBootTest
    public class RetryTest {
    
        @Autowired
        ExamRepository examRepository;
    
        @Test
        void test() {
            for (int i=1;i<=5;i++) {
                String result = examRepository.save(String.valueOf(i));
                log.info("result={}, itemId={}", result, i);
            }
        }
    }
    1~5の合計5回のリクエストが発生し、4回目のリクエスト以降に異常が発生したことを確認します.

    📌 適用後のテスト


    再試行ロジックを適用する方法にproceed()を貼り付け、AOPを適用した.
    @Repository
    public class ExamRepository {
    
        private static int sequence = 0;
    
        // 5번째 요청마다 예외를 발생시킨다.
        @Retry
        public String save(String itemId) {
            ++sequence;
            if (sequence%5 == 0) {
                throw new IllegalStateException("예외 발생");
            }
            return "ok";
        }
    }
    スプリング容器で再試行が必要なので、@Retryを使用して登録します.
    @Import(RetryAspect.class)
    @Slf4j
    @SpringBootTest
    public class RetryTest {
    	...
    }
    これで、5番目のリクエストごとに再試行が行われ、リクエストは正常な応答を再試行されます.

    4番目のリクエストでは、5番目のリクエストが成功したことを確認できます.

    📌 リファレンス


    以下のコースを100%参考にまとめた内容です.
    私は授業資料をそのまま持ってきたわけではありません.もっと正確な情報がほしいなら、私の授業を聞いてください.(強くお勧め!)
    インフラ-スプリングコア原理高級編(金英漢)