내부 호출 문제(Self-invocation): 동일 클래스 내 메서드 간 호출 시 AOP 미작동

상위 맥락

04. AOP 한계와 설계 주의 < 01. AOP와 빈 관리 원리 < 01. Spring MVC1 기초와 아키텍처 < MVC < Spring < Web

핵심 정리

내부 호출 문제는 Spring AOP에서 가장 주의해야 할 한계입니다. 같은 클래스 안에서 한 메서드가 다른 메서드를 직접 호출하면, 그 호출은 프록시를 거치지 않습니다. 따라서 호출되는 메서드에 @Async, @Cacheable, 트랜잭션, 로깅 같은 AOP 기능이 붙어 있어도 동작하지 않습니다.

왜 발생하는가

외부 객체가 스프링 빈을 호출할 때는 프록시가 중간에 개입할 수 있습니다. 하지만 타겟 객체 내부에서 this.methodB()처럼 자기 자신의 메서드를 호출하면, 이는 순수한 자바 내부 호출입니다. 프록시는 이 내부 흐름을 볼 수 없습니다.

예를 들어 외부에서 methodA()가 호출되고, methodA() 안에서 methodB()를 호출한다고 하자. methodA() 호출은 프록시를 통과할 수 있지만, methodA() 내부의 methodB() 호출은 타겟 객체 안에서 직접 발생합니다. 이 경우 methodB()에 붙은 Advice는 실행되지 않습니다.

조용한 실패가 위험한 이유

이 문제는 컴파일 에러나 명확한 런타임 예외로 드러나지 않을 수 있습니다. 기능만 조용히 무시됩니다.

  • @Async가 붙었는데 실제로는 동기 실행됩니다.
  • @Cacheable이 붙었는데 매번 실제 메서드가 실행됩니다.
  • 트랜잭션이나 로깅 같은 부가 기능이 기대와 다르게 적용되지 않음.

이런 문제는 코드만 보면 어노테이션이 붙어 있기 때문에 더 발견하기 어렵습니다.

MVC 설계에서의 영향

컨트롤러나 서비스 내부에 많은 로직을 몰아넣고, 그 안에서 헬퍼 메서드를 직접 호출하는 구조는 내부 호출 문제를 만들기 쉽습니다. 특히 “메서드만 분리하면 되겠지”라고 생각하고 같은 클래스 안에 @Async나 @Cacheable 메서드를 추가하면 의도한 기능이 동작하지 않을 수 있습니다.

해결 방향

AOP가 필요한 로직은 같은 클래스 내부 메서드로 두지 말고 별도의 스프링 빈으로 분리해야 합니다. 이 원칙은 구조 설계 변경에서 정리합니다.

한 문장 요약

Self-invocation 문제는 동일 객체 내부의 직접 메서드 호출이 프록시를 우회하기 때문에 AOP가 조용히 적용되지 않는 현상입니다.

핵심 연결

AOP, 프록시와 함께 읽으면 좋습니다.