Spring 애플리케이션에서 @Cacheable 어노테이션을 사용해 캐싱을 적용하려고 했는데, 기대했던 대로 동작하지 않아 당황한 적이 있나요? 특히 같은 클래스 내에서 @Cacheable 메서드를 호출했을 때 캐싱이 전혀 적용되지 않는 현상을 경험했을 수 있습니다.
이 글에서는 @Cacheable이 예상대로 동작하지 않는 이유와, 이 문제를 해결하는 방법을 심도 있게 다루어 보겠습니다. Spring의 빈(Bean)과 프록시(Proxy)의 동작 원리를 이해하고, 이를 통해 캐싱이 정상적으로 작동하도록 애플리케이션을 설계하는 방법을 알아보세요.
# Spring의 빈(Bean)과 각 어노테이션
Bean이란?
Spring에서 **빈(Bean)**은 Spring IoC 컨테이너에 의해 관리되는 객체를 의미합니다. 빈들은 애플리케이션이 실행될 때 Spring에 의해 자동으로 생성되고, 필요할 때마다 의존성 주입(Dependency Injection)을 통해 다른 객체에 전달됩니다.
Bean 으로 관리된다는 의미
Spring이 클래스를 빈으로 관리한다는 것은, 해당 클래스의 객체 생명주기(생성, 초기화, 소멸 등)를 Spring 컨테이너가 책임진다는 것을 의미합니다. 또한, Spring은 이 빈들을 필요에 따라 주입하거나(Auto-wiring), 다양한 AOP 기능을 적용할 수 있습니다.
Spring에서 자주 사용하는 다른 빈 관련 어노테이션들
- @Component: 모든 Spring 관리 빈에 사용될 수 있는 일반적인 어노테이션으로, @Service, @Repository, @Controller 등의 어노테이션은 @Component의 특수화된 형태입니다.
- @Repository: 데이터 접근 계층(DAO)의 클래스를 나타내는 데 사용되며, 예외 변환 메커니즘이 적용됩니다.
- @Controller: Spring MVC에서 웹 요청을 처리하는 클래스에 사용되며, 주로 HTTP 요청을 매핑하기 위한 메서드를 정의합니다.
# @Cacheable 어노테이션과 프록시 동작 방식
@Cacheable 어노테이션의 역할
@Cacheable 어노테이션은 메서드의 반환값을 캐시에 저장하여, 이후 동일한 파라미터로 호출되는 경우 캐시된 결과를 반환하도록 하는 역할을 합니다. 이 어노테이션이 붙은 메서드의 클래스는 프록시로 감싸지며, 해당 메서드 호출 시 캐시를 먼저 확인하고, 캐시된 값이 있으면 메서드를 실행하지 않고 캐시된 값을 반환합니다.
@Service
public class UserService {
@Cacheable(value = "userCache", key = "#userId")
public User findUserByUserId(String userId) {
// 데이터베이스에서 사용자 정보를 조회하는 로직
return userRepository.findByUserId(userId)
.orElseThrow(() -> new UserNotFoundException("User not found"));
}
public boolean aa(){
return ture;
}
}
위 코드에서 @Cacheable 어노테이션이 붙은 findUserByUserId 메서드 덕분에, UserService 빈이 프록시로 감싸지게 됩니다. 이 프록시는 UserService 의 모든 메서드 호출을 가로채지만, 실제로 @Cacheable이 적용된 메서드에 대해서만 특별한 처리를 수행합니다.
프록시가 감싸진다는 의미
**프록시(Proxy)**는 실제 객체 대신에 메서드 호출을 가로채어 부가적인 작업을 수행하는 객체입니다. @Cacheable 어노테이션이 붙은 메서드가 프록시로 감싸지면, 해당 메서드가 호출될 때마다 프록시가 먼저 캐시를 확인하고, 필요에 따라 캐시된 값을 반환하거나 메서드를 실행합니다.
Spring에서 프록시는 빈 단위로 생성됩니다. 즉, @Cacheable이 붙은 메서드가 있는 클래스 전체가 프록시로 감싸지며, 이 프록시는 클래스의 모든 메서드 호출을 가로채지만, @Cacheable 어노테이션이 붙은 메서드에 대해서만 특별한 처리를 합니다.
동작 원리
- 빈 단위의 프록시: Spring에서는 @Cacheable 같은 AOP 어노테이션이 적용되면, 해당 어노테이션이 붙은 메서드를 포함하는 클래스 전체가 프록시로 감싸집니다. 즉, UserService 빈이 프록시로 감싸지며, 이 프록시가 UserService의 모든 메서드 호출을 가로챕니다.
- 프록시의 역할: 프록시는 findUserByUserId 같은 AOP 어노테이션이 적용된 메서드를 호출할 때, 캐시를 먼저 확인하는 등 부가적인 작업을 수행합니다. 다른 메서드가 호출될 때는 특별한 처리를 하지 않고, 원래 메서드를 그대로 호출합니다.
# 같은 클래스 안에서 @Cacheable 메서드를 직접 호출할 때 발생하는 문제
문제 상황
Spring AOP는 프록시 기반으로 동작하기 때문에, 같은 클래스 안에서 @Cacheable 메서드를 직접 호출하면 프록시가 개입하지 않게 됩니다. 이로 인해 @Cacheable 어노테이션이 제대로 동작하지 않는 문제가 발생할 수 있습니다.
@Service
public class UserService {
public void performUserActions(String userId) {
// 같은 클래스 내에서 @Cacheable 메서드 호출
User user = findUserByUserId(userId); // 프록시가 개입하지 않음
// 추가적인 사용자 작업 수행
}
@Cacheable(value = "userCache", key = "#userId")
public User findUserByUserId(String userId) {
// 데이터베이스에서 사용자 정보를 조회하는 로직
return userRepository.findByUserId(userId)
.orElseThrow(() -> new UserNotFoundException("User not found"));
}
}
위 코드에서 performUserActions 메서드는 같은 클래스 내의 findUserByUserId 메서드를 호출하고 있습니다. 이 경우, findUserByUserId는 프록시를 거치지 않고 직접 호출되기 때문에 @Cacheable 어노테이션이 동작하지 않습니다.
원인과 설명
Spring에서 AOP 프록시는 외부에서 호출될 때만 개입합니다. 같은 클래스 내에서 메서드를 호출할 때는 해당 메서드가 프록시가 아닌 원래 메서드로 직접 호출되기 때문에, AOP 어노테이션이 적용되지 않습니다.
'Java' 카테고리의 다른 글
[Redis] 직렬화 (0) | 2024.08.26 |
---|---|
[Spring] 생성자 주입이 필요한 이유 (0) | 2024.08.23 |
[Redis] Redis란? (0) | 2024.08.16 |
[String Boot] JWT 토큰 저장 HTTP-only Cookie (0) | 2024.08.09 |
[Spring Boot] Spring Boot란? (0) | 2024.08.05 |