상황
일반적으로 캐쉬를 사용할 때 아래와 같은 형태로 사용한다.
@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.yml
에 spring.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"
}
}
}
}
}