๐Ÿ”ฅ TIL - Day 83 Spring AOP๋ฅผ ์ด์šฉํ•œ ์˜ˆ์™ธ๋ฐœ์ƒ ์‹œ ์žฌ์‹œ๋„ ๊ตฌํ˜„

14813 ๋‹จ์–ด Spring AOPSpringSpring

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% ์ฐธ๊ณ ํ•˜์—ฌ ์ •๋ฆฌํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.
๊ฐ•์˜์ž๋ฃŒ๋ฅผ ๊ทธ๋Œ€๋กœ ๊ฐ€์ ธ์˜จ ๊ฒƒ์€ ์•„๋‹ˆ๋‹ˆ ๋ณด๋‹ค ์ •ํ™•ํ•œ ์ •๋ณด๋ฅผ ์›ํ•œ๋‹ค๋ฉด ๊ฐ•์˜๋ฅผ ๋“ค์–ด์ฃผ์„ธ์š”. (๊ฐ•์ถ”!)
์ธํ”„๋Ÿฐ - ์Šคํ”„๋ง ํ•ต์‹ฌ ์›๋ฆฌ ๊ณ ๊ธ‰ํŽธ (๊น€์˜ํ•œ ๋‹˜)

์ข‹์€ ์›นํŽ˜์ด์ง€ ์ฆ๊ฒจ์ฐพ๊ธฐ