πŸ”₯ TIL - Day 83 Spring AOPλ₯Ό μ΄μš©ν•œ μ˜ˆμ™Έλ°œμƒ μ‹œ μž¬μ‹œλ„ κ΅¬ν˜„

Kim Dae HyunΒ·2022λ…„ 1μ›” 23일
0

Spring-AOP

λͺ©λ‘ 보기
5/6

Exception이 λ°œμƒν–ˆμ„ λ•Œ μžλ™μœΌλ‘œ ν•΄λ‹Ή λ©”μ„œλ“œλ₯Ό λ‹€μ‹œ ν˜ΈμΆœν•΄μ£ΌλŠ” ν”„λ‘μ‹œλ₯Ό Spring AOPλ₯Ό μ΄μš©ν•΄μ„œ κ΅¬ν˜„ν•œλ‹€.

νŠΉμ • μš”μ²­μ΄ μž¬μ‹œλ„μ— μ˜ν•΄ μ˜ˆμ™Έλ₯Ό ν”Όν•  수 μžˆλŠ” κ°€λŠ₯성이 μžˆλ‹€λ©΄ μ„œλ²„ μΈ‘μ—μ„œ 정해진 횟수둜 μž¬μ‹œλ„ν•˜λŠ” 것은 ν΄λΌμ΄μ–ΈνŠΈ μž…μž₯μ—μ„œ λ‚˜μ˜μ§€ μ•Šμ€ 방식이닀.

원본 μ½”λ“œλ₯Ό 건듀지 μ•Šκ³  μ μš©ν•˜κ³ μž ν•˜λŠ” λ©”μ„œλ“œμ— @Retry λ₯Ό λΆ™μ—¬μ£ΌλŠ” κ²ƒλ§ŒμœΌλ‘œ μ μš©λ˜λ„λ‘ ν•  것이닀.

원본 μ½”λ“œλ₯Ό 건듀지 μ•Šκ³  λΉ„μ¦ˆλ‹ˆμŠ€ 둜직이 μ•„λ‹Œ μž¬μ‹œλ„ λ‘œμ§μ„ μΆ”κ°€ν•˜λŠ” κ²ƒμ΄λ―€λ‘œ ν”„λ‘μ‹œ νŒ¨ν„΄μ΄ μ λ‹Ήν•˜κ³  ν”„λ‘μ‹œ νŒ¨ν„΄ 을 Springμ—μ„œ 보닀 μ‰½κ²Œ μ‚¬μš©ν•˜κΈ° μœ„ν•΄ Spring AOPλ₯Ό μ‚¬μš©ν•œλ‹€.


πŸ“Œ μ˜μ‘΄μ„± μΆ”κ°€

[ Spring AOP ]

implementation 'org.springframework.boot:spring-boot-starter-aop'

πŸ“Œ μž¬μ‹œλ„ λŒ€μƒμ΄ 될 ν…ŒμŠ€νŠΈ 클래슀 및 λ©”μ„œλ“œ μž‘μ„±

repository의 saveλ©”μ„œλ“œλŠ” 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번째 μš”μ²­λ§ˆλ‹€ μ˜ˆμ™Έκ°€ λ¦¬ν„΄λ˜μ§€λ§Œ μ˜ˆμ™Έλ°œμƒ μ‹œ μž¬μ‹œλ„λ₯Ό 톡해 μ˜ˆμ™Έλ₯Ό νšŒν”Όν•˜λ„λ‘ ν•  것이닀.

πŸ“Œ JoinPointκ°€ 될 λ§ˆν‚Ήμš© Annotation μž‘μ„±

@Retryκ°€ 뢙은 λ©”μ„œλ“œμ— μž¬μ‹œλ„ Adviceκ°€ μ μš©λ˜λ„λ‘ ν•  것이닀.

성곡할 λ•ŒκΉŒμ§€ λ¬΄ν•œμ •μœΌλ‘œ μž¬μ‹œλ„κ°€ λ˜μ§€ μ•Šλ„λ‘ ν•˜κ³ , 적용될 λ©”μ„œλ“œλ§ˆλ‹€ μ΅œλŒ€ μž¬μ‹œλ„ 횟수λ₯Ό λ‹¬λ¦¬ν•˜κΈ° μœ„ν•΄ μ†μ„±μœΌλ‘œ valueλ₯Ό ν¬ν•¨ν•œλ‹€.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Retry {

    int value() default 3;
}

πŸ“Œ Aspect μž‘μ„±

  • JoinPoint μ „ ν›„λ‘œ μ˜ˆμ™Έμ— λŒ€ν•œ 처리 λ“± 좔가적인 μž‘μ—…μ΄ ν•„μš”ν•˜κΈ° λ•Œλ¬Έμ— @Aroundλ₯Ό μ‚¬μš©ν•œλ‹€.

  • @annotation(μ–΄λ…Έν…Œμ΄μ…˜_이름) μ–΄λ…Έν…Œμ΄μ…˜μ˜ 이름을 μ§€μ •ν•˜κ³  ν•΄λ‹Ή νƒ€μž…μ„ 인자둜 λ°›μœΌλ―€λ‘œ PointCut을 μ§€μ •ν•˜κ³  μžˆλ‹€.

    λ§Œμ•½ μ–΄λ…Έν…Œμ΄μ…˜μ„ 인자둜 받지 μ•ŠλŠ”λ‹€λ©΄ @annotation() μ—λŠ” μ–΄λ…Έν…Œμ΄μ…˜μ˜ νŒ¨ν‚€μ§€ κ²½λ‘œμ™€ νƒ€μž…μ΄ λ“€μ–΄κ°€μ•Ό ν•œλ‹€.

  • Retry μ–΄λ…Έν…Œμ΄μ…˜μ—μ„œ μ΅œλŒ€ μž¬μ‹œλ„ 횟수λ₯Ό κ°€μ Έμ˜€κ³  μž¬μ‹œλ„ 횟수만큼 JoinPointλ₯Ό ν˜ΈμΆœν•œλ‹€. (proceed());

  • μš”μ²­ 쀑 μ˜ˆμ™Έκ°€ λ°œμƒν•œλ‹€λ©΄ λ°”λ‘œ μ˜ˆμ™Έλ₯Ό λ˜μ§€μ§€ μ•Šκ³  μΌμ‹œμ μœΌλ‘œ μ €μž₯ν•œ λ‹€μŒ μ΅œλŒ€ μž¬μ‹œλ„ 횟수λ₯Ό μ΄ˆκ³Όν•˜λŠ” 경우 μž„μ‹œ μ €μž₯ν•œ μ˜ˆμ™Έλ₯Ό λ°œμƒμ‹œν‚¨λ‹€.

@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번째 μš”μ²­ 이후 μ˜ˆμ™Έκ°€ ν„°μ§€λŠ” 것을 ν™•μΈν•œλ‹€.

πŸ“Œ μ–΄λ…Έν…Œμ΄μ…˜ 적용 ν›„ ν…ŒμŠ€νŠΈ

μž¬μ‹œλ„ λ‘œμ§μ„ μ μš©ν•˜κ³ μž ν•˜λŠ” λ©”μ„œλ“œμ— @Retryλ₯Ό λΆ™μ—¬μ£Όλ―€λ‘œ 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";
    }
}

μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆμ— μž¬μ‹œλ„ Aspectλ₯Ό μ˜¬λ €μ€˜μ•Ό ν•˜λ―€λ‘œ @Importλ₯Ό μ‚¬μš©ν•΄μ„œ λ“±λ‘ν•œλ‹€.

@Import(RetryAspect.class)
@Slf4j
@SpringBootTest
public class RetryTest {
	...
}

이제 5번째 μš”μ²­λ§ˆλ‹€ μž¬μ‹œλ„κ°€ λ°œμƒν•˜κ³  μž¬μ‹œλ„μ— μ˜ν•΄ μš”μ²­μ΄ μ •μƒμ μœΌλ‘œ 응닡될 것이닀.

4번째 μš”μ²­μ—μ„œ ν•œ 번으둜 μž¬μ‹œλ„κ°€ λ°œμƒν•˜κ³  5번째 μš”μ²­μ΄ μ„±κ³΅ν•œ 것을 확인할 수 μžˆλ‹€.


πŸ“Œ μ°Έκ³ 

μ•„λž˜ κ°•μ˜λ₯Ό 100% μ°Έκ³ ν•˜μ—¬ μ •λ¦¬ν•œ λ‚΄μš©μž…λ‹ˆλ‹€.
κ°•μ˜μžλ£Œλ₯Ό κ·ΈλŒ€λ‘œ κ°€μ Έμ˜¨ 것은 μ•„λ‹ˆλ‹ˆ 보닀 μ •ν™•ν•œ 정보λ₯Ό μ›ν•œλ‹€λ©΄ κ°•μ˜λ₯Ό λ“€μ–΄μ£Όμ„Έμš”. (κ°•μΆ”!)
μΈν”„λŸ° - μŠ€ν”„λ§ 핡심 원리 κ³ κΈ‰νŽΈ (κΉ€μ˜ν•œ λ‹˜)

profile
μ’€ 더 천천히 까먹기 μœ„ν•΄ κΈ°λ‘ν•©λ‹ˆλ‹€. 🧐

0개의 λŒ“κΈ€