In the blog post we look into the unless
property of the @Cacheable
Spring annotation when using a custom key generator.
We have a custom key generator where we generate the cache key from the Authentication
object stored in the SecurityContext
@Component("customKeyGenerator")
@Log4j2
class CustomKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
String username = SecurityContextHolder.getContext().getAuthentication().getName();
log.info("CustomKeyGenerator called with '{}'", username);
return new SimpleKey(username);
}
}
With the @CacheConfig
annotation we are using the previous custom key generator.
@Service
@Log4j2
@CacheConfig(keyGenerator = "customKeyGenerator")
class CustomerService {
@Cacheable(value = "customers", unless = "@monitoring.monitoringUser()")
public Customer findOne1() {
log.info("CustomerService was called");
return new Customer("customer");
}
}
With the unless
property of the @Cacheable
we can veto the adding of a customer to the customers
cache.
In the example we don’t want to cache customers which are used for monitoring.
The unless
property accepts a boolean expression which is evaluated after the method has been called.
With the @
sign we can reference a method of a bean
@Component(value = "monitoring")
@Log4j2
class Monitoring {
@Value("${caching.disable.users:#{T(java.util.Collections).emptyList()}}")
private List<String> users;
public boolean monitoringUser() {
String name = SecurityContextHolder.getContext().getAuthentication().getName();
// do not cache
if (users.contains(name)) return true;
return false;
}
}
The running example you can find it here: https://github.com/altfatterz/conditional-caching-demo/
We configured couple of these users where caching needs to be skipped
caching:
disable:
users: bean, hancock
After building the project and starting the application we can access the
http :8080/customer
but we receive a 401 Unauthorized
error. The endpoint needs a JWT token. The included JwtTokenGenerator
helps to create couple of valid JWT token for testing.
Here is one for the bean
user:
export HEADER='Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJiZWFuIiwiYXV0aG9yaXRpZXMiOiJ1c2VyIn0.SM8rLETjXuo8xrrn2OwDb99EcxYHUI7DYZXL271ZdMM'
http :8080/customer $HEADER
In the logs we see:
2019-07-24 22:00:52.962 TRACE 38569 --- [nio-8080-exec-7] o.s.cache.interceptor.CacheInterceptor : Computed cache key 'SimpleKey [bean]' for operation Builder[public com.example.Customer com.example.CustomerService.findOne()] caches=[customers] | key='' | keyGenerator='customKeyGenerator' | cacheManager='' | cacheResolver='' | condition='' | unless='@monitoring.isMonitoringUser()' | sync='false'
2019-07-24 22:00:52.963 TRACE 38569 --- [nio-8080-exec-7] o.s.cache.interceptor.CacheInterceptor : No cache entry for key 'SimpleKey [bean]' in cache(s) [customers]
2019-07-24 22:00:52.963 INFO 38569 --- [nio-8080-exec-7] com.example.CustomKeyGenerator : CustomKeyGenerator called with 'bean'
2019-07-24 22:00:52.963 TRACE 38569 --- [nio-8080-exec-7] o.s.cache.interceptor.CacheInterceptor : Computed cache key 'SimpleKey [bean]' for operation Builder[public com.example.Customer com.example.CustomerService.findOne()] caches=[customers] | key='' | keyGenerator='customKeyGenerator' | cacheManager='' | cacheResolver='' | condition='' | unless='@monitoring.isMonitoringUser()' | sync='false'
2019-07-24 22:00:52.966 INFO 38569 --- [nio-8080-exec-7] com.example.CustomerService : CustomerService was called
If we call the endpoint again we see that the CustomerService
is called again, so it is not cached.
2019-07-24 22:03:02.349 INFO 38569 --- [nio-8080-exec-1] com.example.CustomKeyGenerator : CustomKeyGenerator called with 'bean'
2019-07-24 22:03:02.349 TRACE 38569 --- [nio-8080-exec-1] o.s.cache.interceptor.CacheInterceptor : Computed cache key 'SimpleKey [bean]' for operation Builder[public com.example.Customer com.example.CustomerService.findOne()] caches=[customers] | key='' | keyGenerator='customKeyGenerator' | cacheManager='' | cacheResolver='' | condition='' | unless='@monitoring.isMonitoringUser()' | sync='false'
2019-07-24 22:03:02.349 TRACE 38569 --- [nio-8080-exec-1] o.s.cache.interceptor.CacheInterceptor : No cache entry for key 'SimpleKey [bean]' in cache(s) [customers]
2019-07-24 22:03:02.349 INFO 38569 --- [nio-8080-exec-1] com.example.CustomKeyGenerator : CustomKeyGenerator called with 'bean'
2019-07-24 22:03:02.349 TRACE 38569 --- [nio-8080-exec-1] o.s.cache.interceptor.CacheInterceptor : Computed cache key 'SimpleKey [bean]' for operation Builder[public com.example.Customer com.example.CustomerService.findOne()] caches=[customers] | key='' | keyGenerator='customKeyGenerator' | cacheManager='' | cacheResolver='' | condition='' | unless='@monitoring.isMonitoringUser()' | sync='false'
2019-07-24 22:03:02.349 INFO 38569 --- [nio-8080-exec-1] com.example.CustomerService : CustomerService was called
There is also the condition
property of the @Cacheble
annotation which works opposite. If the expression is true
then the method return value is cached.