Spring之Cache缓存详解
Posted 敲代码的小小酥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring之Cache缓存详解相关的知识,希望对你有一定的参考价值。
前言
Spring的Cache缓存类似于java的JDBC。是定义了一套规范。第三方缓存需要实现这套规范,才能通过Spring API使用缓存功能。这套规范的核心接口是CacheManager和Cache。其中,CacheMananger是获取Cache的入口。Cache是实现缓存逻辑的接口。下面具体看这两个接口。
CacheManager
我们先看其源码:
public interface CacheManager {
/**
* Get the cache associated with the given name.
* <p>Note that the cache may be lazily created at runtime if the
* native provider supports it.
* @param name the cache identifier (must not be {@code null})
* @return the associated cache, or {@code null} if such a cache
* does not exist or could be not created
*/
@Nullable
Cache getCache(String name);
/**
* Get a collection of the cache names known by this manager.
* @return the names of all caches known by the cache manager
*/
Collection<String> getCacheNames();
}
可以看到,接口中定义了两个方法,getCache方法是根据指定的名称返回缓存对象。getCacheNames方法是返回缓存管理器中所有的缓存对象。由此可知缓存管理器的作用就是获取到缓存对象。
Spring为我们提供了最简单的CacheManager实现类ConcurrentMapCacheManager。我们看其实现方式。
public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
private boolean dynamic = true;
private boolean allowNullValues = true;
private boolean storeByValue = false;
@Nullable
private SerializationDelegate serialization;
/**
* Construct a dynamic ConcurrentMapCacheManager,
* lazily creating cache instances as they are being requested.
*/
public ConcurrentMapCacheManager() {
}
/**
* Construct a static ConcurrentMapCacheManager,
* managing caches for the specified cache names only.
*/
public ConcurrentMapCacheManager(String... cacheNames) {
setCacheNames(Arrays.asList(cacheNames));
}
/**
* Specify the set of cache names for this CacheManager's 'static' mode.
* <p>The number of caches and their names will be fixed after a call to this method,
* with no creation of further cache regions at runtime.
* <p>Calling this with a {@code null} collection argument resets the
* mode to 'dynamic', allowing for further creation of caches again.
*/
public void setCacheNames(@Nullable Collection<String> cacheNames) {
if (cacheNames != null) {
for (String name : cacheNames) {
this.cacheMap.put(name, createConcurrentMapCache(name));
}
this.dynamic = false;
}
else {
this.dynamic = true;
}
}
/**
* Specify whether to accept and convert {@code null} values for all caches
* in this cache manager.
* <p>Default is "true", despite ConcurrentHashMap itself not supporting {@code null}
* values. An internal holder object will be used to store user-level {@code null}s.
* <p>Note: A change of the null-value setting will reset all existing caches,
* if any, to reconfigure them with the new null-value requirement.
*/
public void setAllowNullValues(boolean allowNullValues) {
if (allowNullValues != this.allowNullValues) {
this.allowNullValues = allowNullValues;
// Need to recreate all Cache instances with the new null-value configuration...
recreateCaches();
}
}
/**
* Return whether this cache manager accepts and converts {@code null} values
* for all of its caches.
*/
public boolean isAllowNullValues() {
return this.allowNullValues;
}
/**
* Specify whether this cache manager stores a copy of each entry ({@code true}
* or the reference ({@code false} for all of its caches.
* <p>Default is "false" so that the value itself is stored and no serializable
* contract is required on cached values.
* <p>Note: A change of the store-by-value setting will reset all existing caches,
* if any, to reconfigure them with the new store-by-value requirement.
* @since 4.3
*/
public void setStoreByValue(boolean storeByValue) {
if (storeByValue != this.storeByValue) {
this.storeByValue = storeByValue;
// Need to recreate all Cache instances with the new store-by-value configuration...
recreateCaches();
}
}
/**
* Return whether this cache manager stores a copy of each entry or
* a reference for all its caches. If store by value is enabled, any
* cache entry must be serializable.
* @since 4.3
*/
public boolean isStoreByValue() {
return this.storeByValue;
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.serialization = new SerializationDelegate(classLoader);
// Need to recreate all Cache instances with new ClassLoader in store-by-value mode...
if (isStoreByValue()) {
recreateCaches();
}
}
@Override
public Collection<String> getCacheNames() {
return Collections.unmodifiableSet(this.cacheMap.keySet());
}
@Override
@Nullable
public Cache getCache(String name) {
Cache cache = this.cacheMap.get(name);
if (cache == null && this.dynamic) {
synchronized (this.cacheMap) {
cache = this.cacheMap.get(name);
if (cache == null) {
cache = createConcurrentMapCache(name);
this.cacheMap.put(name, cache);
}
}
}
return cache;
}
private void recreateCaches() {
for (Map.Entry<String, Cache> entry : this.cacheMap.entrySet()) {
entry.setValue(createConcurrentMapCache(entry.getKey()));
}
}
/**
* Create a new ConcurrentMapCache instance for the specified cache name.
* @param name the name of the cache
* @return the ConcurrentMapCache (or a decorator thereof)
*/
protected Cache createConcurrentMapCache(String name) {
SerializationDelegate actualSerialization = (isStoreByValue() ? this.serialization : null);
return new ConcurrentMapCache(name, new ConcurrentHashMap<>(256), isAllowNullValues(), actualSerialization);
}
}
由源码可以看到,该缓存管理器中通过CurrentHashMap存放着name和Cache对象。通过name获取Cache对象其实就是获取Map对象中的value值。Cache对象也是用的Spring提供的ConcurrentMapCache对象。该对象内部,也是由CurrentMap对象存放缓存数据。由此可知,Spring为我们提供了一个Map形式的缓存。所以在做一些小项目时,可以使用这个简单的缓存管理器来存储缓存。
我们也可以自定义缓存管理器,获取缓存对象,不过像redis这样的厂商已经为我们提供了相应的实现类,所以一般情况下我们无需进行自定义。
Cache接口
public interface Cache {
/**
* Return the cache name.
*/
String getName();
/**
* Return the underlying native cache provider.
*/
Object getNativeCache();
//获取缓存的主要方法
@Nullable
ValueWrapper get(Object key);
@Nullable
<T> T get(Object key, @Nullable Class<T> type);
@Nullable
<T> T get(Object key, Callable<T> valueLoader);
//添加缓存的主要方法
void put(Object key, @Nullable Object value);
@Nullable
default ValueWrapper putIfAbsent(Object key, @Nullable Object value) {
ValueWrapper existingValue = get(key);
if (existingValue == null) {
put(key, value);
}
return existingValue;
}
void evict(Object key);
default boolean evictIfPresent(Object key) {
evict(key);
return false;
}
void clear();
default boolean invalidate() {
clear();
return false;
}
@FunctionalInterface
interface ValueWrapper {
@Nullable
Object get();
}
@SuppressWarnings("serial")
class ValueRetrievalException extends RuntimeException {
@Nullable
private final Object key;
public ValueRetrievalException(@Nullable Object key, Callable<?> loader, Throwable ex) {
super(String.format("Value for key '%s' could not be loaded using '%s'", key, loader), ex);
this.key = key;
}
@Nullable
public Object getKey() {
return this.key;
}
}
}
cache的自定义实现类
public class MapCaffeCache implements Cache {
// private ConcurrentMapCache mapCache = new ConcurrentMapCache("mapCache");
private com.github.benmanes.caffeine.cache.@NonNull Cache<Object, Object> mapCache = Caffeine.newBuilder()
.expireAfterWrite(5, TimeUnit.SECONDS).expireAfterAccess(5, TimeUnit.SECONDS).maximumSize(5).build();
private com.github.benmanes.caffeine.cache.@NonNull Cache<Object, Object> caffeCache = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES).expireAfterAccess(1, TimeUnit.MINUTES).maximumSize(100).build();
@Autowired
private StringRedisTemplate redisTemplate;
private String name = "userCache";
@Override
public String getName() {
return this.name;
}
@Override
public Object getNativeCache() {
return this;
}
@Override
public ValueWrapper get(Object key) {
@Nullable
Object ob = mapCache.getIfPresent(key);
// 如果一级缓存有数据 直接返回 不触发二级缓存
if (ob != null) {
System.out.println(String.format("Cache L1 (CaffeineCache) :: %s = %s", key, ob));
SimpleValueWrapper valueWrapper = new SimpleValueWrapper(ob);
return valueWrapper;
}
Object obj = caffeCache.getIfPresent(key);
if (obj != null) {
SimpleValueWrapper valueWrapper2 = new SimpleValueWrapper(obj);
System.out.println(String.format("Cache L2 (CaffeineCache) :: %s = %s", key, obj));
// 如果二级缓存有数据 则更新到一级缓存
mapCache.put(key, obj);
return valueWrapper2;
}
return null;
}
@Override
public <T> T get(Object key, Class<T> type) {
return (T) get(key).get();
}
@Override
public <T> T get(Object key, Callable<T> valueLoader) {
return (T) get(key).get();
}
@Override
public void put(Object key, Object value) {
mapCache.put(key, value);
caffeCache.put(key, value);
//当nginx搭建集群时 用redis 订阅/发布 功能同步各项目点的缓存一致性
redisTemplate.convertAndSend("ch1", key + ":>" + value);
}
@Override
public void evict(Object key) {
mapCache.asMap().remove(key);
caffeCache.asMap().remove(key);
}
@Override
public void clear() {
mapCache.asMap().clear();
caffeCache.asMap().clear();
}
}
可以看到,接口中定义了put,get等方法,就是在往缓存中存放和取值。该接口就是往各自的第三方缓存中进行存值和取值的规范。这个接口我们一般也不用自定义实现,也是由第三方缓存厂商进行实现。
并且,Cache中存缓存和取缓存的方法,也无需我们自己调用。Spring在缓存切面中,会进行这些方法的调用。所以,使用Spring的缓存,为我们省去了存取缓存的代码。
@Cacheable注解
缓存管理器和缓存对象都有了,Spring是如何进行缓存的存取的呢,这就涉及到我们使用者使用的@Cacheable注解了。
我们在方法上用@Cacheable注解进行修饰,Spring AOP生成代理对象,当调用方法时,代理对象判断是否有缓存,有缓存则调用缓存,不再调用目标方法,没有缓存,则调用目标方法,且将返回值存入缓存中。
@Cacheable的具体用法,请看@Cacheable详解
缓存配置流程
1.使用@EnableCaching注解开启缓存功能。
2.配置CacheManager类
3.配置Cache类
4.使用@Cacheable注解修饰要缓存的方法。
以上是关于Spring之Cache缓存详解的主要内容,如果未能解决你的问题,请参考以下文章
spring cache 学习——@CacheEvict 使用详解