본문 바로가기

Framework/Spring

[Spring] @Cacheable에서 발생하는 self-invocation 문제 - 컴도리돌이

728x90

@Cacheable self-invocation (in effect, a method within the target object calling another method of the target object). The cache annotation will be ignored at runtime

 

@Cacheable 어노테이션을 사용해서 메서드 결과를 캐싱하려고 할 때, 같은 클래스 내의 다른 메서드를 호출하면 캐싱이 적용되지 않는 문제가 발생합니다. 저 역시 처음 이 문제를 겪고는 “왜 캐싱이 안 되지?”라는 의문을 가졌어요. 🤔

 

예를 들어, 아래처럼 getList 메서드에서 getTypeAList 메서드를 호출할 때 @Cacheable을 설정했는데도 캐시가 적용되지 않더라고요

public SiteListResponse getList(String type) throws Exception {
    return getTypeAList(type);
}

@Cacheable(value = "getTypeA", cacheManager = "comdolliManager")
public List<String> getTypeAList(String type) throws Exception {
    // Some logic to retrieve data
    return result;
}

 

왜일까요? 이유는 @Cacheable 어노테이션이 프록시 방식을 사용해 캐싱을 처리하기 때문입니다. Spring에서 @Cacheable은 기본적으로 AOP 프록시 패턴을 통해 메서드를 가로채고 캐시 처리를 수행해요. 이 프록시는 클래스 외부에서 호출될 때만 적용됩니다. 즉 self-invocation이라 불리는 클래스 내부 메서드 간 호출은 프록시를 거치지 않기 때문에 @Cacheable이 무시돼요! 🤓

 

[Spring] AOP(Aspect-Oriented Programming)에 대해서 - 컴도리돌이

개발을 하면서 "이 부분은 코드가 반복되는데, 더 좋은 방법이 없을까"라는 생각을 정말 매 순간 하는 거 같아요. 🥲 그중 하나의 해결 책으로 AOP가 그 해답이 될 수 있어요 🤔AOP(Aspect-Oriented Prog

comdolidol-i.tistory.com

 

그러므로 캐시를 적용하려는 메서드를 같은 클래스가 아닌 다른 빈(Bean)에서 호출하도록 설계해야합니다. 가장 일반적인 방법은 인터페이스를 만들어 getTypeAList 메서드를 호출할 때 Spring이 프록시를 적용할 수 있도록 하는 것이에요. 

 

아래처럼 getTypeAList 메서드를 별도의 서비스 클래스로 분리하면 문제 없이 캐시가 적용됩니다. 

// testService.java
@Service
public class TestService {
    @Cacheable(value = "getTypeA", cacheManager = "comdolliManager")
    public List<String> getTypeAList(String type) throws Exception {
        // Some logic to retrieve data
        return result;
    }
}

// MainService.java
@Service
public class MainService {
    private final TestService testService;

    public MainService(TestService testService) {
        this.testService = testService;
    }

    public SiteListResponse getList(String type) throws Exception {
        return testService.getTypeAList(type);
    }
}

 

이제 getList 메서드가 TestService를 통해 getTypeAList를 호출하게 되었기 때문에, @Cacheable이 정상적으로 작동돼요. 이처럼 Spring의 프록시 메커니즘을 이해하고 설계하는 것이 중요합니다. 자칫 잘못된 방식으로 설계하게 되면 캐싱이 적용되지 않을 뿐 아니라, 코드의 유지보수성에도 영향을 미칠 수 있습니다 🤔