김영한의 스프링 핵심 원리(고급편) - AOP 실무 예제

2025. 2. 25. 20:59·김영한의 스프링 핵심 원리 - 고급편

Question

  • 예외 발생시 재시도를 하는 로직을 AOP를 통해 구현한다면?
  • AOP 사용시 같은 클래스 내부의 메서드 호출시 잘 동작하지 않는 이유는?
  • 위 문제를 해결할 방법은?
  • JDK 동적 프록시와 CGLIB 방식의 장단점은?
  • 스프링이 기본적으로 채택한 프록시 방식은?

 

예외 발생시 재시도를 하는 로직을 AOP를 통해 구현한다면?

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Retry {
	int value() default 3;
}

@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;
    }
}
  • @Retry 어노테이션 생성하고, 재시도 횟수를 value로 지정
  • AOP에서 @annotation으로 조인포인트 맵핑
  • 파라미터로 retry를 받아서, retry에 지정된 value를 가져와 그 횟수만큼 재시도
  • @Around 안에서 joinPoint.proceed()는 여러번 시도 가능

 

 

AOP 사용시 내부 호출이 동작하지 않는 문제

  • 같은 클래스 내의 함수에서 같은 클래스 내의 함수를 호출하면 AOP가 동작하지 않음
  • 예를 들어서, this.method()와 같은 경우
  • 그 이유는, AOP는 프록시를 거쳐서 호출이 되야 하는데, 내부를 호출할 때는 프록시가 동작하지 않기 때문
  • 위 그림에서 external()과 internal()은 같은 클래스 내의 함수
  • 클라이언트가 external()을 호출할 때는 프록시가 만들어지며 프록시의 external이 진짜 실제의 external()을 호출
  • 진짜 실제의 external()은 internal()을 호출할 때 프록시를 거치지 않고 진짜 자신의 internal()을 호출
  • 따라서, 프록시를 거치지 않기 때문에 AOP가 동작하지 않음.
  • AspectJ를 사용하면 AspectJ는 프록시 방식을 사용하지 않고 실제 바이트 코드를 조작해 함수 앞뒤로 어드바이스를 넣어주기 때문에 이러한 문제가 발생하지는 않음

 

프록시 내부 호출 문제 해결 방법

  • 자기 자신을 의존관계 주입을 받는 방법(Setter 방식으로)이 있는데 좋은 방법은 아님
  • ObjectProvider(Provider)를 사용하여 객체를 스프링 컨테이너에서 조회하는 것을 스프링 빈 생성 시점이 아니라 실제 객체를 사용하 는 시점으로 지연 하는 방법
  • 구조를 변경하여 내부를 호출해야 하는 함수를 별도 클래스로 만들어서 사용
  • 일반적으로 3번째 방법이 그나마 나은 경우지만 AOP는 일반적으로 부가기능의 용도이므로 꼭 이렇게 사용해야 할 케이스가 흔하진 않음

 

프록시 기술과 한계 - 타입 캐스팅

  • JDK 동적 프록시 방식은 인터페이스가 있는 경우에만 가능함
  • CGLIB 방식은 구체클래스에도 가능하지만 여러 문제가 있음
    • 대상 클래스에 기본 생성자 필수
    • 생성자 2번 호출 문제
    • final 키워드 클래스, 메서드 사용 불가
  • 하지만 스프링4.0부터는 objenesis라는 특별한 라이브러리를 사용하여 기본 생성자 필수, 생성자 2번 호출 문제를 CGLIB에서 해결함
  • final 키워드는 사실상 잘 사용하지 않으므로 스프링 부트 2.0 부터는 CGLIB을 기본으로 사용하는 방식을 채택

'김영한의 스프링 핵심 원리 - 고급편' 카테고리의 다른 글

김영한의 스프링 핵심 원리(고급편) - 총 정리  (0) 2026.01.19
김영한의 스프링 핵심 원리(고급편) - 포인트컷 지시자(execution 등)  (1) 2025.02.25
김영한의 스프링 핵심 원리(고급편) - AOP의 포인트컷, AOP의 어드바이스 종류  (0) 2025.02.25
김영한의 스프링 핵심 원리(고급편) - @Aspect, AOP  (0) 2025.02.24
김영한의 스프링 핵심 원리(고급편) - 빈 후처리기  (0) 2025.02.22
'김영한의 스프링 핵심 원리 - 고급편' 카테고리의 다른 글
  • 김영한의 스프링 핵심 원리(고급편) - 총 정리
  • 김영한의 스프링 핵심 원리(고급편) - 포인트컷 지시자(execution 등)
  • 김영한의 스프링 핵심 원리(고급편) - AOP의 포인트컷, AOP의 어드바이스 종류
  • 김영한의 스프링 핵심 원리(고급편) - @Aspect, AOP
5jyan5
5jyan5
  • 5jyan5
    jyan
    5jyan5
  • 전체
    오늘
    어제
    • 분류 전체보기 (242)
      • 김영한의 스프링 핵심 원리(기본편) (8)
      • 김영한의 스프링 핵심 원리 - 고급편 (11)
      • 김영한의 스프링 MVC 1편 (1)
      • 김영한의 스프링 DB 1편 (3)
      • 김영한의 스프링 MVC 2편 (3)
      • 김영한의 ORM 표준 JPA 프로그래밍(기본편) (9)
      • 김영한의 스프링 부트와 JPA 활용2 (2)
      • 김영한의 실전 자바 - 중급 1편 (1)
      • 김영한의 실전 자바 - 고급 1편 (9)
      • 김영한의 실전 자바 - 고급 2편 (9)
      • Readable Code: 읽기 좋은 코드를 작성.. (2)
      • 김영한의 실전 자바 - 고급 3편 (9)
      • CKA (118)
      • 개발 (37)
      • 경제 (4)
      • 리뷰 (1)
      • 정보 (2)
  • 블로그 메뉴

    • 링크

    • 공지사항

    • 인기 글

    • 태그

      hibernate5module
      페치 조인
      cglib
      jdk 동적 프록시
      양방향 맵핑
      조회 성능 최적화
      버퍼
      log trace
      Target
      @args
      reentarantlock
      Thread
      JPQL
      단방향 맵핑
      락
      requset scope
      김영한
      @discriminatorcolumn
      프록시
      WAS
      @within
      jpq
      typequery
      @discriminatorvalue
      자바
      스레드
      gesingleresult
      고급
      빈 후처리기
      프록시 팩토리
    • 최근 댓글

    • 최근 글

    • hELLO· Designed By정상우.v4.10.2
    5jyan5
    김영한의 스프링 핵심 원리(고급편) - AOP 실무 예제
    상단으로

    티스토리툴바