본문 바로가기
개발자 준비/Spring

스프링 AOP StackOverflowError (feat.스프링 AOP 동작원리)

by osul_world 2022. 2. 10.
728x90

AOP StackOverflowError

AOP를 적용하는 과정에서 다음과 같은 이슈가 발생했다.

org.springframework.beans.factory.BeanCreationException ... 

스프링 빈 생성에 예외가 발생했다는 의미인데,

 

Caused by 를 보니 아래와 같았다.

이 로그 이후로도 쭉 반복적으로 순환호출하는 양상을 보였다.

java.lang.StackOverflowError : null

 

StackOverflowError가 터진것을 보아 어디선가 무한순환을 유발하는 굴래가 존재한다는것 같다.

 

AOP를 위해 만든 LogTraceAspect 즉 AOP 설정 클래스를 계속 호출하는것을 확인할수있다.

 

LogTraceAspect 코드

@Component //컴포넌트 스캔: 빈 등록
@Aspect
@Slf4j
public class LogTraceAspect {

    private final LogTrace logTrace;

    public LogTraceAspect(LogTrace logTrace) { // <-ThreadLocalLogTrace 주입 받아 사용
        this.logTrace = logTrace;
    }

    @Around("execution(* PU.puservice..*.*(..))")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        TraceStatus status = null;

        try {
            String message = joinPoint.getSignature().toShortString();
            status = logTrace.begin(message);  //여기!! 문제 발생 지점

            Object result = joinPoint.proceed();

            logTrace.end(status);
            return result;
        } catch (Exception e) {
            logTrace.exception(status, e);
            throw e;
        }
    }
}

로그를 살펴보니 LogTraceAspect의 30 번째 줄 에서 문제가 발생했다.

 

의존관계 주입 받은 ThreadLocalLogTrace를 처음으로 호출한 부분이다.

status = logTrace.begin(message); 

 

 

문제해결

이 문제를 해결하려면 스프링 AOP가 어떤식으로 동작하는지 원리를 알고있어야한다.

 

스프링 AOP는 런타임에 동적 프록시를 생성해서 실제 객체 대신 빈 후처리기를 통해 생성한 프록시 객체를 등록한다.

이렇게 프록시를 생성할 대상과 그 기능을 정의한 클래스를 AOP 설정 클래스(어드바이저) 라고한다.

 

@Aspect가 붙은 클래스, 여기선 LogTraceAspect

 

이 AOP 설정 클래스를 빈에 등록해야 사용할수있다.

 

 

원인분석

 

aspect 폴더는 proj.project. 패키지 하위에 존재하고있다.

@Around("execution(* proj.project..*.*(..))") //포인트컷 AspectJ 표현식

그런데 이 부분을 보면 AOP가 AOP 설정 클래스가 담긴 aspect폴더 자체에도 적용되도록 포인트컷이 설정되어있음을 알수있다.

 

그래서 AOP 설정 클래스인 LogTraceAspect를 빈에 등록하려고 보니 ThreadLocalLogTrace를 주입해야해서 ThreadLocalLogTrace를 빈에 등록하려고 보니 AOP를 적용하도록 포인트컷이 설정되어있어 LogTraceAspect를 적용해야한다.

그래서 다시 LogTraceAspect를 빈에 등록하려고 보니 ThreadLocalLogTrace를 주입해야한다...무한 반복@@@

 

에초에 AOP 설정 클래스를 빈에 등록해야 AOP를 적용하는데 AOP 설정 클래스가 AOP 대상이 되는것 자체가 말이 안된다.

결론은 포인트컷에 && !execution(* proj.project.aspect..*.*(..)) 를 추가해서 AOP 설정 클래스 관련은 AOP대상에서 제외시켜주면 된다.

@Component
@Aspect
@Slf4j
public class LogTraceAspect {

    private final LogTrace logTrace;

    public LogTraceAspect(LogTrace logTrace) {
        this.logTrace = logTrace;
    }

    //!execution(* proj.project.aspect..*.*(..)) 추가!
    @Around("execution(* proj.project..*.*(..)) && !execution(* proj.project.aspect..*.*(..))") 
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        TraceStatus status = null;

        try {
            String message = joinPoint.getSignature().toShortString();
            status = logTrace.begin(message);  

            Object result = joinPoint.proceed();

            logTrace.end(status);
            return result;
        } catch (Exception e) {
            logTrace.exception(status, e);
            throw e;
        }
    }
}

 

정리

AOP 설정 클래스에 AOP를 적용하도록 포인트컷이 적용되어있었기 때문에, AOP 설정 클래스를 빈에 등록하려고 AOP를 적용해야하는데

 

AOP를 적용하려면 AOP 설정 클래스가 빈에 등록되어야 하기때문에 무한이 반복

 

빈 등록이 진행되지 못하고 스택오버플로에 빠져 발생한 문제이다.

 

결론은, AOP 설정 클래스는 포인트컷에서 제외시켜야한다.

728x90