diff --git a/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCache.java b/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCache.java index 4b2127cd28c..34dc622f8b2 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCache.java +++ b/spring-context-support/src/main/java/org/springframework/cache/caffeine/CaffeineCache.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -123,11 +123,23 @@ public class CaffeineCache extends AbstractValueAdaptingCache { this.cache.invalidate(key); } + @Override + public boolean evictIfPresent(Object key) { + return (this.cache.asMap().remove(key) != null); + } + @Override public void clear() { this.cache.invalidateAll(); } + @Override + public boolean invalidate() { + boolean notEmpty = !this.cache.asMap().isEmpty(); + this.cache.invalidateAll(); + return notEmpty; + } + private class PutIfAbsentFunction implements Function { diff --git a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheCache.java b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheCache.java index 9403026f1f5..26ea8929d3a 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheCache.java +++ b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheCache.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -72,6 +72,19 @@ public class EhCacheCache implements Cache { return toValueWrapper(element); } + @SuppressWarnings("unchecked") + @Override + @Nullable + public T get(Object key, @Nullable Class type) { + Element element = this.cache.get(key); + Object value = (element != null ? element.getObjectValue() : null); + if (value != null && type != null && !type.isInstance(value)) { + throw new IllegalStateException( + "Cached value is not of required type [" + type.getName() + "]: " + value); + } + return (T) value; + } + @SuppressWarnings("unchecked") @Override @Nullable @@ -95,7 +108,6 @@ public class EhCacheCache implements Cache { this.cache.releaseWriteLockOnKey(key); } } - } private T loadValue(Object key, Callable valueLoader) { @@ -110,19 +122,6 @@ public class EhCacheCache implements Cache { return value; } - @Override - @SuppressWarnings("unchecked") - @Nullable - public T get(Object key, @Nullable Class type) { - Element element = this.cache.get(key); - Object value = (element != null ? element.getObjectValue() : null); - if (value != null && type != null && !type.isInstance(value)) { - throw new IllegalStateException( - "Cached value is not of required type [" + type.getName() + "]: " + value); - } - return (T) value; - } - @Override public void put(Object key, @Nullable Object value) { this.cache.put(new Element(key, value)); @@ -140,11 +139,23 @@ public class EhCacheCache implements Cache { this.cache.remove(key); } + @Override + public boolean evictIfPresent(Object key) { + return this.cache.remove(key); + } + @Override public void clear() { this.cache.removeAll(); } + @Override + public boolean invalidate() { + boolean notEmpty = (this.cache.getSize() > 0); + this.cache.removeAll(); + return notEmpty; + } + @Nullable private Element lookup(Object key) { diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/JCacheCache.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/JCacheCache.java index b9599bca879..97e3e45cd6b 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/JCacheCache.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/JCacheCache.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -105,11 +105,23 @@ public class JCacheCache extends AbstractValueAdaptingCache { this.cache.remove(key); } + @Override + public boolean evictIfPresent(Object key) { + return this.cache.remove(key); + } + @Override public void clear() { this.cache.removeAll(); } + @Override + public boolean invalidate() { + boolean notEmpty = this.cache.iterator().hasNext(); + this.cache.removeAll(); + return notEmpty; + } + private class ValueLoaderEntryProcessor implements EntryProcessor { diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractCacheInterceptor.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractCacheInterceptor.java index 8f0d4d56e65..06025fc26a1 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractCacheInterceptor.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/AbstractCacheInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -59,7 +59,7 @@ abstract class AbstractCacheInterceptor, A /** * Resolve the cache to use. * @param context the invocation context - * @return the cache to use (never null) + * @return the cache to use (never {@code null}) */ protected Cache resolveCache(CacheOperationInvocationContext context) { Collection caches = context.getOperation().getCacheResolver().resolveCaches(context); @@ -73,7 +73,7 @@ abstract class AbstractCacheInterceptor, A /** * Convert the collection of caches in a single expected element. *

Throw an {@link IllegalStateException} if the collection holds more than one element - * @return the single element or {@code null} if the collection is empty + * @return the single element, or {@code null} if the collection is empty */ @Nullable static Cache extractFrom(Collection caches) { diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheRemoveAllInterceptor.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheRemoveAllInterceptor.java index 34446bf4baa..66b583e3787 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheRemoveAllInterceptor.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheRemoveAllInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,7 +42,6 @@ class CacheRemoveAllInterceptor extends AbstractCacheInterceptor context, CacheOperationInvoker invoker) { CacheRemoveAllOperation operation = context.getOperation(); - boolean earlyRemove = operation.isEarlyRemove(); if (earlyRemove) { removeAll(context); @@ -67,10 +66,10 @@ class CacheRemoveAllInterceptor extends AbstractCacheInterceptor context) { Cache cache = resolveCache(context); if (logger.isTraceEnabled()) { - logger.trace("Invalidating entire cache '" + cache.getName() + "' for operation " - + context.getOperation()); + logger.trace("Invalidating entire cache '" + cache.getName() + "' for operation " + + context.getOperation()); } - doClear(cache); + doClear(cache, context.getOperation().isEarlyRemove()); } } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheRemoveEntryInterceptor.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheRemoveEntryInterceptor.java index 22836578076..84852783166 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheRemoveEntryInterceptor.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/interceptor/CacheRemoveEntryInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,7 +42,6 @@ class CacheRemoveEntryInterceptor extends AbstractKeyCacheInterceptor context, CacheOperationInvoker invoker) { CacheRemoveOperation operation = context.getOperation(); - boolean earlyRemove = operation.isEarlyRemove(); if (earlyRemove) { removeValue(context); @@ -68,10 +67,10 @@ class CacheRemoveEntryInterceptor extends AbstractKeyCacheInterceptor { } /** - * Specify if the cache entry should be remove before invoking the method. By default, the - * cache entry is removed after the method invocation. + * Specify if the cache entry should be removed before invoking the method. + *

By default, the cache entry is removed after the method invocation. * @see javax.cache.annotation.CacheRemove#afterInvocation() */ public boolean isEarlyRemove() { diff --git a/spring-context-support/src/main/java/org/springframework/cache/transaction/TransactionAwareCacheDecorator.java b/spring-context-support/src/main/java/org/springframework/cache/transaction/TransactionAwareCacheDecorator.java index 9038156aacf..ca193ee0d8f 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/transaction/TransactionAwareCacheDecorator.java +++ b/spring-context-support/src/main/java/org/springframework/cache/transaction/TransactionAwareCacheDecorator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,14 +25,16 @@ import org.springframework.transaction.support.TransactionSynchronizationManager import org.springframework.util.Assert; /** - * Cache decorator which synchronizes its {@link #put}, {@link #evict} and {@link #clear} - * operations with Spring-managed transactions (through Spring's {@link TransactionSynchronizationManager}, - * performing the actual cache put/evict/clear operation only in the after-commit phase of a - * successful transaction. If no transaction is active, {@link #put}, {@link #evict} and + * Cache decorator which synchronizes its {@link #put}, {@link #evict} and + * {@link #clear} operations with Spring-managed transactions (through Spring's + * {@link TransactionSynchronizationManager}, performing the actual cache + * put/evict/clear operation only in the after-commit phase of a successful + * transaction. If no transaction is active, {@link #put}, {@link #evict} and * {@link #clear} operations will be performed immediately, as usual. * - *

Use of more aggressive operations such as {@link #putIfAbsent} cannot be deferred - * to the after-commit phase of a running transaction. Use these with care. + *

Note: Use of immediate operations such as {@link #putIfAbsent} and + * {@link #evictIfPresent} cannot be deferred to the after-commit phase of a + * running transaction. Use these with care in a transactional environment. * * @author Juergen Hoeller * @author Stephane Nicoll @@ -54,6 +56,7 @@ public class TransactionAwareCacheDecorator implements Cache { this.targetCache = targetCache; } + /** * Return the target Cache that this Cache should delegate to. */ @@ -124,6 +127,11 @@ public class TransactionAwareCacheDecorator implements Cache { } } + @Override + public boolean evictIfPresent(Object key) { + return this.targetCache.evictIfPresent(key); + } + @Override public void clear() { if (TransactionSynchronizationManager.isSynchronizationActive()) { @@ -139,4 +147,9 @@ public class TransactionAwareCacheDecorator implements Cache { } } + @Override + public boolean invalidate() { + return this.targetCache.invalidate(); + } + } diff --git a/spring-context-support/src/test/java/org/springframework/cache/transaction/TransactionAwareCacheDecoratorTests.java b/spring-context-support/src/test/java/org/springframework/cache/transaction/TransactionAwareCacheDecoratorTests.java index 8d1bd568afd..0a57e89801b 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/transaction/TransactionAwareCacheDecoratorTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/transaction/TransactionAwareCacheDecoratorTests.java @@ -21,25 +21,23 @@ import org.junit.Test; import org.springframework.cache.Cache; import org.springframework.cache.concurrent.ConcurrentMapCache; import org.springframework.tests.transaction.CallCountingTransactionManager; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.interceptor.DefaultTransactionAttribute; +import org.springframework.transaction.support.TransactionTemplate; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; /** * @author Stephane Nicoll + * @author Juergen Hoeller */ public class TransactionAwareCacheDecoratorTests { - private final PlatformTransactionManager txManager = new CallCountingTransactionManager(); + private final TransactionTemplate txTemplate = new TransactionTemplate(new CallCountingTransactionManager()); + @Test public void createWithNullTarget() { - assertThatIllegalArgumentException().isThrownBy(() -> - new TransactionAwareCacheDecorator(null)); + assertThatIllegalArgumentException().isThrownBy(() -> new TransactionAwareCacheDecorator(null)); } @Test @@ -79,20 +77,18 @@ public class TransactionAwareCacheDecoratorTests { public void putTransactional() { Cache target = new ConcurrentMapCache("testCache"); Cache cache = new TransactionAwareCacheDecorator(target); - - TransactionStatus status = this.txManager.getTransaction( - new DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED)); - Object key = new Object(); - cache.put(key, "123"); - assertThat(target.get(key)).isNull(); - this.txManager.commit(status); + + txTemplate.execute(() -> { + cache.put(key, "123"); + assertThat(target.get(key)).isNull(); + }); assertThat(target.get(key, String.class)).isEqualTo("123"); } @Test - public void putIfAbsent() { // no transactional support for putIfAbsent + public void putIfAbsentNonTransactional() { Cache target = new ConcurrentMapCache("testCache"); Cache cache = new TransactionAwareCacheDecorator(target); @@ -104,6 +100,23 @@ public class TransactionAwareCacheDecoratorTests { assertThat(target.get(key, String.class)).isEqualTo("123"); } + @Test + public void putIfAbsentTransactional() { // no transactional support for putIfAbsent + Cache target = new ConcurrentMapCache("testCache"); + Cache cache = new TransactionAwareCacheDecorator(target); + Object key = new Object(); + + txTemplate.execute(() -> { + assertThat(cache.putIfAbsent(key, "123")).isNull(); + assertThat(target.get(key, String.class)).isEqualTo("123"); + assertThat(cache.putIfAbsent(key, "456").get()).isEqualTo("123"); + // unchanged + assertThat(target.get(key, String.class)).isEqualTo("123"); + }); + + assertThat(target.get(key, String.class)).isEqualTo("123"); + } + @Test public void evictNonTransactional() { Cache target = new ConcurrentMapCache("testCache"); @@ -122,12 +135,36 @@ public class TransactionAwareCacheDecoratorTests { Object key = new Object(); cache.put(key, "123"); + txTemplate.execute(() -> { + cache.evict(key); + assertThat(target.get(key, String.class)).isEqualTo("123"); + }); - TransactionStatus status = this.txManager.getTransaction( - new DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED)); - cache.evict(key); - assertThat(target.get(key, String.class)).isEqualTo("123"); - this.txManager.commit(status); + assertThat(target.get(key)).isNull(); + } + + @Test + public void evictIfPresentNonTransactional() { + Cache target = new ConcurrentMapCache("testCache"); + Cache cache = new TransactionAwareCacheDecorator(target); + Object key = new Object(); + cache.put(key, "123"); + + cache.evictIfPresent(key); + assertThat(target.get(key)).isNull(); + } + + @Test + public void evictIfPresentTransactional() { // no transactional support for evictIfPresent + Cache target = new ConcurrentMapCache("testCache"); + Cache cache = new TransactionAwareCacheDecorator(target); + Object key = new Object(); + cache.put(key, "123"); + + txTemplate.execute(() -> { + cache.evictIfPresent(key); + assertThat(target.get(key)).isNull(); + }); assertThat(target.get(key)).isNull(); } @@ -150,13 +187,38 @@ public class TransactionAwareCacheDecoratorTests { Object key = new Object(); cache.put(key, "123"); - - TransactionStatus status = this.txManager.getTransaction( - new DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED)); - cache.clear(); - assertThat(target.get(key, String.class)).isEqualTo("123"); - this.txManager.commit(status); + txTemplate.execute(() -> { + cache.clear(); + assertThat(target.get(key, String.class)).isEqualTo("123"); + }); assertThat(target.get(key)).isNull(); } + + @Test + public void invalidateNonTransactional() { + Cache target = new ConcurrentMapCache("testCache"); + Cache cache = new TransactionAwareCacheDecorator(target); + Object key = new Object(); + cache.put(key, "123"); + + cache.invalidate(); + assertThat(target.get(key)).isNull(); + } + + @Test + public void invalidateTransactional() { // no transactional support for invalidate + Cache target = new ConcurrentMapCache("testCache"); + Cache cache = new TransactionAwareCacheDecorator(target); + Object key = new Object(); + cache.put(key, "123"); + + txTemplate.execute(() -> { + cache.invalidate(); + assertThat(target.get(key)).isNull(); + }); + + assertThat(target.get(key)).isNull(); + } + } diff --git a/spring-context/src/main/java/org/springframework/cache/Cache.java b/spring-context/src/main/java/org/springframework/cache/Cache.java index 988eed98063..dea4505d9ad 100644 --- a/spring-context/src/main/java/org/springframework/cache/Cache.java +++ b/spring-context/src/main/java/org/springframework/cache/Cache.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,6 +55,7 @@ public interface Cache { * a cached {@code null} value. A straight {@code null} being * returned means that the cache contains no mapping for this key. * @see #get(Object, Class) + * @see #get(Object, Callable) */ @Nullable ValueWrapper get(Object key); @@ -94,6 +95,7 @@ public interface Cache { * @return the value to which this cache maps the specified key * @throws ValueRetrievalException if the {@code valueLoader} throws an exception * @since 4.3 + * @see #get(Object) */ @Nullable T get(Object key, Callable valueLoader); @@ -102,8 +104,13 @@ public interface Cache { * Associate the specified value with the specified key in this cache. *

If the cache previously contained a mapping for this key, the old * value is replaced by the specified value. + *

Actual registration may be performed in an asynchronous or deferred + * fashion, with subsequent lookups possibly not seeing the entry yet. + * This may for example be the case with transactional cache decorators. + * Use {@link #putIfAbsent} for guaranteed immediate registration. * @param key the key with which the specified value is to be associated * @param value the value to be associated with the specified key + * @see #putIfAbsent(Object, Object) */ void put(Object key, @Nullable Object value); @@ -112,19 +119,19 @@ public interface Cache { * if it is not set already. *

This is equivalent to: *


-	 * Object existingValue = cache.get(key);
+	 * ValueWrapper existingValue = cache.get(key);
 	 * if (existingValue == null) {
 	 *     cache.put(key, value);
-	 *     return null;
-	 * } else {
-	 *     return existingValue;
 	 * }
+	 * return existingValue;
 	 * 
* except that the action is performed atomically. While all out-of-the-box * {@link CacheManager} implementations are able to perform the put atomically, * the operation may also be implemented in two steps, e.g. with a check for * presence and a subsequent put, in a non-atomic way. Check the documentation * of the native cache implementation that you are using for more details. + *

The default implementation delegates to {@link #get(Object)} and + * {@link #put(Object, Object)} along the lines of the code snippet above. * @param key the key with which the specified value is to be associated * @param value the value to be associated with the specified key * @return the value to which this cache maps the specified key (which may be @@ -132,21 +139,73 @@ public interface Cache { * mapping for that key prior to this call. Returning {@code null} is therefore * an indicator that the given {@code value} has been associated with the key. * @since 4.1 + * @see #put(Object, Object) */ @Nullable - ValueWrapper putIfAbsent(Object key, @Nullable Object value); + default ValueWrapper putIfAbsent(Object key, @Nullable Object value) { + ValueWrapper existingValue = get(key); + if (existingValue == null) { + put(key, value); + } + return existingValue; + } /** * Evict the mapping for this key from this cache if it is present. + *

Actual eviction may be performed in an asynchronous or deferred + * fashion, with subsequent lookups possibly still seeing the entry. + * This may for example be the case with transactional cache decorators. + * Use {@link #evictIfPresent} for guaranteed immediate removal. * @param key the key whose mapping is to be removed from the cache + * @see #evictIfPresent(Object) */ void evict(Object key); /** - * Remove all mappings from the cache. + * Evict the mapping for this key from this cache if it is present, + * expecting the key to be immediately invisible for subsequent lookups. + *

The default implementation delegates to {@link #evict(Object)}, + * returning {@code false} for not-determined prior presence of the key. + * Cache providers and in particular cache decorators are encouraged + * to perform immediate eviction if possible (e.g. in case of generally + * deferred cache operations within a transaction) and to reliably + * determine prior presence of the given key. + * @param key the key whose mapping is to be removed from the cache + * @return {@code true} if the cache was known to have a mapping for + * this key before, {@code false} if it did not (or if prior presence + * could not be determined) + * @since 5.2 + * @see #evict(Object) + */ + default boolean evictIfPresent(Object key) { + evict(key); + return false; + } + + /** + * Clear the cache through removing all mappings. + *

Actual clearing may be performed in an asynchronous or deferred + * fashion, with subsequent lookups possibly still seeing the entries. + * This may for example be the case with transactional cache decorators. + * Use {@link #invalidate()} for guaranteed immediate removal of entries. + * @see #invalidate() */ void clear(); + /** + * Invalidate the cache through removing all mappings, expecting all + * entries to be immediately invisible for subsequent lookups. + * @return {@code true} if the cache was known to have mappings before, + * {@code false} if it did not (or if prior presence of entries could + * not be determined) + * @since 5.2 + * @see #clear() + */ + default boolean invalidate() { + clear(); + return false; + } + /** * A (wrapper) object representing a cache value. diff --git a/spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java b/spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java index bbfc6526d57..5aa39788521 100644 --- a/spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java +++ b/spring-context/src/main/java/org/springframework/cache/annotation/CacheEvict.java @@ -142,7 +142,7 @@ public @interface CacheEvict { * occur irrespective of the method outcome (i.e., whether it threw an * exception or not). *

Defaults to {@code false}, meaning that the cache eviction operation - * will occur after the advised method is invoked successfully (i.e., + * will occur after the advised method is invoked successfully (i.e. * only if the invocation did not throw an exception). */ boolean beforeInvocation() default false; diff --git a/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCache.java b/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCache.java index b92d6e7eefc..6b3906fa88d 100644 --- a/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCache.java +++ b/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCache.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -141,7 +141,7 @@ public class ConcurrentMapCache extends AbstractValueAdaptingCache { @Override @Nullable public T get(Object key, Callable valueLoader) { - return (T) fromStoreValue(this.store.computeIfAbsent(key, r -> { + return (T) fromStoreValue(this.store.computeIfAbsent(key, k -> { try { return toStoreValue(valueLoader.call()); } @@ -168,11 +168,23 @@ public class ConcurrentMapCache extends AbstractValueAdaptingCache { this.store.remove(key); } + @Override + public boolean evictIfPresent(Object key) { + return (this.store.remove(key) != null); + } + @Override public void clear() { this.store.clear(); } + @Override + public boolean invalidate() { + boolean notEmpty = !this.store.isEmpty(); + this.store.clear(); + return notEmpty; + } + @Override protected Object toStoreValue(@Nullable Object userValue) { Object storeValue = super.toStoreValue(userValue); diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractCacheInvoker.java b/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractCacheInvoker.java index 45f9742b2d8..4710f8e1c30 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractCacheInvoker.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/AbstractCacheInvoker.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -92,12 +92,17 @@ public abstract class AbstractCacheInvoker { } /** - * Execute {@link Cache#evict(Object)} on the specified {@link Cache} and - * invoke the error handler if an exception occurs. + * Execute {@link Cache#evict(Object)}/{@link Cache#evictIfPresent(Object)} on the + * specified {@link Cache} and invoke the error handler if an exception occurs. */ - protected void doEvict(Cache cache, Object key) { + protected void doEvict(Cache cache, Object key, boolean immediate) { try { - cache.evict(key); + if (immediate) { + cache.evictIfPresent(key); + } + else { + cache.evict(key); + } } catch (RuntimeException ex) { getErrorHandler().handleCacheEvictError(ex, cache, key); @@ -108,9 +113,14 @@ public abstract class AbstractCacheInvoker { * Execute {@link Cache#clear()} on the specified {@link Cache} and * invoke the error handler if an exception occurs. */ - protected void doClear(Cache cache) { + protected void doClear(Cache cache, boolean immediate) { try { - cache.clear(); + if (immediate) { + cache.invalidate(); + } + else { + cache.clear(); + } } catch (RuntimeException ex) { getErrorHandler().handleCacheClearError(ex, cache); diff --git a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java index 1bbf9016008..bbb6b1b9832 100644 --- a/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java +++ b/spring-context/src/main/java/org/springframework/cache/interceptor/CacheAspectSupport.java @@ -485,14 +485,14 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker for (Cache cache : context.getCaches()) { if (operation.isCacheWide()) { logInvalidating(context, operation, null); - doClear(cache); + doClear(cache, operation.isBeforeInvocation()); } else { if (key == null) { key = generateKey(context, result); } logInvalidating(context, operation, key); - doEvict(cache, key); + doEvict(cache, key, operation.isBeforeInvocation()); } } } diff --git a/spring-context/src/main/java/org/springframework/cache/support/NoOpCache.java b/spring-context/src/main/java/org/springframework/cache/support/NoOpCache.java index fd413115617..e3e54862a86 100644 --- a/spring-context/src/main/java/org/springframework/cache/support/NoOpCache.java +++ b/spring-context/src/main/java/org/springframework/cache/support/NoOpCache.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -93,8 +93,18 @@ public class NoOpCache implements Cache { public void evict(Object key) { } + @Override + public boolean evictIfPresent(Object key) { + return false; + } + @Override public void clear() { } + @Override + public boolean invalidate() { + return false; + } + }