Revised handling of allowNullValues for asynchronous retrieval
Includes revised cacheNames javadoc and equals/hashCode for SimpleValueWrapper. See gh-31637
This commit is contained in:
@@ -116,21 +116,23 @@ public interface Cache {
|
||||
* <p>Can return {@code null} if the cache can immediately determine that
|
||||
* it contains no mapping for this key (e.g. through an in-memory key map).
|
||||
* Otherwise, the cached value will be returned in the {@link CompletableFuture},
|
||||
* with {@code null} indicating a late-determined cache miss (and a nested
|
||||
* {@link ValueWrapper} potentially indicating a nullable cached value).
|
||||
* with {@code null} indicating a late-determined cache miss. A nested
|
||||
* {@link ValueWrapper} potentially indicates a nullable cached value;
|
||||
* the cached value may also be represented as a plain element if null
|
||||
* values are not supported. Calling code needs to be prepared to handle
|
||||
* all those variants of the result returned by this method.
|
||||
* @param key the key whose associated value is to be returned
|
||||
* @return the value to which this cache maps the specified key, contained
|
||||
* within a {@link CompletableFuture} which may also be empty when a cache
|
||||
* miss has been late-determined. A straight {@code null} being returned
|
||||
* means that the cache immediately determined that it contains no mapping
|
||||
* for this key. A {@link ValueWrapper} contained within the
|
||||
* {@code CompletableFuture} can indicate a cached value that is potentially
|
||||
* {@code CompletableFuture} indicates a cached value that is potentially
|
||||
* {@code null}; this is sensible in a late-determined scenario where a regular
|
||||
* CompletableFuture-contained {@code null} indicates a cache miss. However,
|
||||
* an early-determined cache will usually return the plain cached value here,
|
||||
* and a late-determined cache may also return a plain value if it does not
|
||||
* support the actual caching of {@code null} values. Spring's common cache
|
||||
* processing can deal with all variants of these implementation strategies.
|
||||
* a cache may also return a plain value if it does not support the actual
|
||||
* caching of {@code null} values, avoiding the extra level of value wrapping.
|
||||
* Spring's cache processing can deal with all such implementation strategies.
|
||||
* @since 6.1
|
||||
* @see #retrieve(Object, Supplier)
|
||||
*/
|
||||
@@ -149,11 +151,14 @@ public interface Cache {
|
||||
* <p>If possible, implementations should ensure that the loading operation
|
||||
* is synchronized so that the specified {@code valueLoader} is only called
|
||||
* once in case of concurrent access on the same key.
|
||||
* <p>If the {@code valueLoader} throws an exception, it will be propagated
|
||||
* <p>Null values are generally not supported by this method. The provided
|
||||
* {@link CompletableFuture} handle produces a value or raises an exception.
|
||||
* If the {@code valueLoader} raises an exception, it will be propagated
|
||||
* to the {@code CompletableFuture} handle returned from here.
|
||||
* @param key the key whose associated value is to be returned
|
||||
* @return the value to which this cache maps the specified key,
|
||||
* contained within a {@link CompletableFuture}
|
||||
* @return the value to which this cache maps the specified key, contained
|
||||
* within a {@link CompletableFuture} which will never be {@code null}.
|
||||
* The provided future is expected to produce a value or raise an exception.
|
||||
* @since 6.1
|
||||
* @see #retrieve(Object)
|
||||
* @see #get(Object, Callable)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -32,6 +32,7 @@ import java.lang.annotation.Target;
|
||||
* @author Stephane Nicoll
|
||||
* @author Sam Brannen
|
||||
* @since 4.1
|
||||
* @see Cacheable
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -42,8 +43,10 @@ public @interface CacheConfig {
|
||||
* Names of the default caches to consider for caching operations defined
|
||||
* in the annotated class.
|
||||
* <p>If none is set at the operation level, these are used instead of the default.
|
||||
* <p>May be used to determine the target cache (or caches), matching the
|
||||
* qualifier value or the bean names of a specific bean definition.
|
||||
* <p>Names may be used to determine the target cache(s), to be resolved via the
|
||||
* configured {@link #cacheResolver()} which typically delegates to
|
||||
* {@link org.springframework.cache.CacheManager#getCache}.
|
||||
* For further details see {@link Cacheable#cacheNames()}.
|
||||
*/
|
||||
String[] cacheNames() default {};
|
||||
|
||||
|
||||
@@ -70,8 +70,17 @@ public @interface Cacheable {
|
||||
|
||||
/**
|
||||
* Names of the caches in which method invocation results are stored.
|
||||
* <p>Names may be used to determine the target cache (or caches), matching
|
||||
* the qualifier value or bean name of a specific bean definition.
|
||||
* <p>Names may be used to determine the target cache(s), to be resolved via the
|
||||
* configured {@link #cacheResolver()} which typically delegates to
|
||||
* {@link org.springframework.cache.CacheManager#getCache}.
|
||||
* <p>This will usually be a single cache name. If multiple names are specified,
|
||||
* they will be consulted for a cache hit in the order of definition, and they
|
||||
* will all receive a put/evict request for the same newly cached value.
|
||||
* <p>Note that asynchronous/reactive cache access may not fully consult all
|
||||
* specified caches, depending on the target cache. In the case of late-determined
|
||||
* cache misses (e.g. with Redis), further caches will not get consulted anymore.
|
||||
* As a consequence, specifying multiple cache names in an async cache mode setup
|
||||
* only makes sense with early-determined cache misses (e.g. with Caffeine).
|
||||
* @since 4.2
|
||||
* @see #value
|
||||
* @see CacheConfig#cacheNames
|
||||
|
||||
@@ -160,7 +160,8 @@ public class ConcurrentMapCache extends AbstractValueAdaptingCache {
|
||||
@Nullable
|
||||
public CompletableFuture<?> retrieve(Object key) {
|
||||
Object value = lookup(key);
|
||||
return (value != null ? CompletableFuture.completedFuture(fromStoreValue(value)) : null);
|
||||
return (value != null ? CompletableFuture.completedFuture(
|
||||
isAllowNullValues() ? toValueWrapper(value) : fromStoreValue(value)) : null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.cache.Cache;
|
||||
@@ -35,11 +36,15 @@ import org.springframework.lang.Nullable;
|
||||
* the set of cache names is pre-defined through {@link #setCacheNames}, with no
|
||||
* dynamic creation of further cache regions at runtime.
|
||||
*
|
||||
* <p>Supports the asynchronous {@link Cache#retrieve(Object)} and
|
||||
* {@link Cache#retrieve(Object, Supplier)} operations through basic
|
||||
* {@code CompletableFuture} adaptation, with early-determined cache misses.
|
||||
*
|
||||
* <p>Note: This is by no means a sophisticated CacheManager; it comes with no
|
||||
* cache configuration options. However, it may be useful for testing or simple
|
||||
* caching scenarios. For advanced local caching needs, consider
|
||||
* {@link org.springframework.cache.jcache.JCacheCacheManager} or
|
||||
* {@link org.springframework.cache.caffeine.CaffeineCacheManager}.
|
||||
* {@link org.springframework.cache.caffeine.CaffeineCacheManager} or
|
||||
* {@link org.springframework.cache.jcache.JCacheCacheManager}.
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.1
|
||||
|
||||
@@ -508,7 +508,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
|
||||
|
||||
if (cacheHit != null && !hasCachePut(contexts)) {
|
||||
// If there are no put requests, just use the cache hit
|
||||
cacheValue = (cacheHit instanceof Cache.ValueWrapper wrapper ? wrapper.get() : cacheHit);
|
||||
cacheValue = unwrapCacheValue(cacheHit);
|
||||
returnValue = wrapCacheValue(method, cacheValue);
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.cache.support;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.cache.Cache.ValueWrapper;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -50,4 +52,19 @@ public class SimpleValueWrapper implements ValueWrapper {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
return (this == other || (other instanceof ValueWrapper wrapper && Objects.equals(get(), wrapper.get())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(this.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ValueWrapper for [" + this.value + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user