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缓存详解的主要内容,如果未能解决你的问题,请参考以下文章

Yii2片段缓存详解

spring Cache注解详解

spring cache 学习——@CacheEvict 使用详解

转:Spring Cache抽象详解

spring cache 学习 —— @Cacheable 使用详解

Spring Cache