spring / / 2023. 9. 22. 09:17

Spring Cache에서 value의 이름을 동적으로 변경할 수 있을까?

상황

일반적으로 캐쉬를 사용할 때 아래와 같은 형태로 사용한다.

@Cacheable(value = "user", key = "#userId")
public User find(int userId) {
    return userRepository.findById(userId)
        .map(UserJpo::toDomain)
        .orElseThrow(() -> new IllegalArgumentException());
}

이런 경우 key의 경우 파라미터의 값을 받아 처리하도록 변수처리를 할 수 있지만, value에 들어가는 값을 동적으로 변경할 수 있을까?

예를 들면 아래와 같이 실행해보자.

spring.application.name을 value에 추가하여 application.yml의 프로퍼티 정보를 읽어서 표시하도록 한번 해보자.

@Cacheable(value = "${spring.application.name}-user", key = "#userId")

application.ymlspring.application.name=cache-app으로 등록한다.

이렇게 하고 재기동 후 cacheName을 actuator를 통해 조회를 해보면 아래와 같이 변경이 되지 않는다.

http://localhost:8080/actuator/caches

{
  "cacheManagers": {
    "cacheManager": {
      "caches": {
        "${spring.application.name}-user": {
          "target": "java.util.concurrent.ConcurrentHashMap"
        }
      }
    }
  }
}

변수처리가 되기를 기대했던 ${spring.application.name}값이 그대로 문자열로 사용된다.

해결책

이를 위한 해결방법으로 CachingConfigurerSupport를 통해 cacheResolver를 재구현하면 된다.

@Configuration
@RequiredArgsConstructor
public class CacheResolverConfig extends CachingConfigurerSupport {

    private final CacheManager cacheManager;

    private final Environment environment;

    @Override
    public CacheResolver cacheResolver() {
        return new PropertyResolvingCacheResolver(cacheManager, environment);
    }
}
public class PropertyResolvingCacheResolver extends SimpleCacheResolver {

    private final PropertyResolver propertyResolver;

    protected PropertyResolvingCacheResolver(CacheManager cacheManager, PropertyResolver propertyResolver) {
        super(cacheManager);
        this.propertyResolver = propertyResolver;
    }

    @Override
    protected Collection<String> getCacheNames(CacheOperationInvocationContext<?> context) {
        String packageName = context.getTarget().getClass().getPackage().getName();
        Collection<String> cacheNames = super.getCacheNames(context);

        return cacheNames.stream()
            .map(propertyResolver::resolvePlaceholders)
            .collect(Collectors.toList());
    }
}

위와 같이 CacheResolverConfig를 추가하고 다시 실행해보자. (spring.application.name=cache-app)

{
  "cacheManagers": {
    "cacheManager": {
      "caches": {
        "cache-app-user": {
          "target": "java.util.concurrent.ConcurrentHashMap"
        }
      }
    }
  }
}

user cache 이름 앞에 application name으로 변경되는 것을 확인할 수 있다.

일괄 변경 방법

만일 @Cacheable은 건드리지 않고 모든 캐쉬의 이름앞에 일괄적으로 붙이고 싶다면 아래와 같이 변경하면 된다.

@Cacheable(value = "user", key = "#userId")

value는 user로만 설정한다.

그리고 PropertyResolvingCacheResolver에 getCacheNames에 applicationName을 추가한다.

@Override
protected Collection<String> getCacheNames(CacheOperationInvocationContext<?> context) {
    String packageName = context.getTarget().getClass().getPackage().getName();
    Collection<String> cacheNames = super.getCacheNames(context);
    String applicationName = this.propertyResolver.getProperty("spring.application.name");

    return cacheNames.stream()
        .map(cacheName -> {
            String resolvedCacheName = propertyResolver.resolveRequiredPlaceholders(cacheName);

            return applicationName + "-" + resolvedCacheName;
        }).collect(Collectors.toList());
}

이렇게 해도 아래와 같은 결과가 나타난다.

{
  "cacheManagers": {
    "cacheManager": {
      "caches": {
        "cache-app-user": {
          "target": "java.util.concurrent.ConcurrentHashMap"
        }
      }
    }
  }
}
반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유