聊聊Spring Cache的缓存抽象与JSR107缓存抽象JCache,并使用API方式使用Spring Cache【享学Spring】

每篇一句

不考虑业务场景的架构都是耍流氓

前言

缓存(Cache)是计算机领域一个极其重要的概念,它是提高硬件(比如CPU、显卡)、软件运行效率非常重要且有效的一个手段,它的最大特点就一个字:速度非常快

缓存就是数据交换的缓冲区(称作:Cache),当要读取数据时,会首先从缓存汇总查询数据,有则直接执行返回,速度飞快。它被运用在计算机领域的各个方面,介绍如下:

  • 操作系统磁盘缓存 ——> 减少磁盘机械操作
  • Web服务器缓存——>减少应用服务器请求
  • 客户端浏览器缓存——>减少对网站的访问
  • 应用程序缓存——>减少对数据库的查询
  • 数据库缓存——>减少文件系统IO

本文讲解的缓存就是运用在我们应用程序(软件)上的缓存,并且主要指的是在Spring环境下对缓存的使用。随着Spring框架的普及和渗透,在Spring应用中使用缓存,应该成为了当下Java开发者必备的一个基础技能了~

本文主要讲解Spring对缓存的抽象,当然也会对JSR107缓存抽象进行概念性的介绍。

JSR107缓存抽象:JCache

说起JSR107或者说是JCache,估摸大多数小伙伴都会觉得非常的陌生,没用过且还没听过。

JSR107的草案提得其实是非常的早的,但是第一个Final Release版本却一直难产到了2014年,如图(本文截自JSR官网):
在这里插入图片描述
虽然最终它还是被作为JSR规范提出了,但那时已经4102年了,黄瓜菜早就凉凉~

在还没有缓存规范出来之前,作为Java市场标准制定的强有力竞争者:Spring框架动作频频,早在2011年就提供了它自己的缓存抽象(Spring3.1)。这一切依托于Spring的良好生态下,各大缓存厂商纷纷提供了实现产品。

因此目前而言,关于缓存这块业界有个通识:

  • Spring Cache缓存抽象已经成了业界实际的标准(几乎所有产品都支持)
  • JSR107仅仅只是官方的标准而已(支持的产品并不多)

因为JSR107使用得极少,因此此处对它只做比较简单的一个概念介绍即可。

若要使用JCache,首先我们得额外导包(API包):

<dependency>
    <groupId>javax.cachegroupId>
    <artifactId>cache-apiartifactId>
    <version>1.1.1version>
dependency>

2019年5月发布的最新的1.1.1版本。1.0.0版本是2014年5月发布的

聊聊Spring Cache的缓存抽象与JSR107缓存抽象JCache,并使用API方式使用Spring Cache【享学Spring】_第1张图片

从这个jar的类里可以看到,它几乎所有都是接口,自己并不提供具体实现(第三方厂商自行实现)。

JCache的实现产品挺少的,Ehcache3.x有实现JSR107相关规范接口

它的核心类的层次结构图:
聊聊Spring Cache的缓存抽象与JSR107缓存抽象JCache,并使用API方式使用Spring Cache【享学Spring】_第2张图片

  • CachingProvider:创建、配置、获取、管理和控制多个CacheManager
  • CacheManager:创建、配置、获取、管理和控制多个唯一命名的Cache。(一个CacheManager仅被一个CachingProvider所拥有
  • Cache:一个类似Map的数据结构。(一个Cache仅被一个CacheManager所拥有
  • Entry:一个存储在Cache中的key-value对
  • Expiry每一个存储在Cache中的条目有一个定义的有效期,过期后不可访问、更新、删除。缓存有效期可以通过ExpiryPolicy设置

说实话,我个人认为JCache的这个设计太大而全了,导致我们使用它的复杂度是非常高的,因此难以流行起来。(其实JavaEE的很多设计都有这个通病,标准过于复杂,落地实操性很差~)

我看网上有小伙伴评论说:JSR107的设计简直莫名其妙。
其实啊,针对这种评论一定要辩证性的看待,毕竟JSR是全球顶级专家一起制定的,整体优秀性我觉得是毋庸置疑的,只是它作为标准,它不能对那20%的场景避而不谈,而Spring却可以,这就是差别~

Spring缓存抽象

上面说了JCache真正发布都到2014年了,而早在2011年Spring3.1版本就定义了它自己的缓存抽象,旨在帮助开发者简化缓存的开发,并且最终流行开来。
聊聊Spring Cache的缓存抽象与JSR107缓存抽象JCache,并使用API方式使用Spring Cache【享学Spring】_第3张图片
从截图中可以看到,它被定义在spring-context里面的,作为上下文的核心内容,并不需要额外导包

Spring的缓存抽象相关类的层次结构非常简单:
聊聊Spring Cache的缓存抽象与JSR107缓存抽象JCache,并使用API方式使用Spring Cache【享学Spring】_第4张图片

  • CacheManager:缓存管理器。管理各种缓存(Cache)组件
  • Cache:为缓存的组件规范定义,包含缓存的各种操作集合。比如它有很多实现:ConcurrentMapCacheRedisCacheEhCacheCache(额外导包)

说明:看到这个层次结构,很多小伙伴会问为何没有Expire的定义?
这里我想说:这也是我比较费解的地方之一。Expire作为缓存非常重要的能力,为何不抽象出来呢???这也是我们经常苦恼的地方:@Cacheable注解竟然不支持TTL过期时间的设置,着实让人很蛋疼~~~

我个人把Spring没有Expire这个理解为Spring缓存抽象的一个功能缺失,说不客气点就是Spring的一个Bug。不知各位对此是否和我相同意见呢?欢迎砸我讨论

至于为何它一直都没有“修复”?我感觉是因为Cache属于它对外公布的API,各大产品都自己实现了Expire,而且方式不尽相同,所以最终它想统一就很难了,很难做到最好的兼容性~

CacheManagerCache的使用示例

CacheManager简单描述就是用来存放Cache,Cache用于存放具体的key-value值。
比如一个名为"汽车厂"的Cache,那你就可以通过这个名字从CacheManager拿出这个Cache,然后往里面缓存汽车。

首先看看CacheManager这个接口:

// pring's central cache manager SPI.  它是个SPI接口
// @since 3.1
public interface CacheManager {
	@Nullable
	Cache getCache(String name);
	// 管理的所有的Cache的names~
	Collection<String> getCacheNames();
}

它的继承树如下(不进行任何额外导包的情况下):
聊聊Spring Cache的缓存抽象与JSR107缓存抽象JCache,并使用API方式使用Spring Cache【享学Spring】_第5张图片
聊聊Spring Cache的缓存抽象与JSR107缓存抽象JCache,并使用API方式使用Spring Cache【享学Spring】_第6张图片
这些实现是Spring内置的最基础的缓存管理器类。

AbstractCacheManager
// @since 3.1 实现了InitializingBean接口~~
public abstract class AbstractCacheManager implements CacheManager, InitializingBean {
	
	// 保存着所有的Cache对象~~key为名字
	private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);	
	//  此处使用了volatile 关键字
	private volatile Set<String> cacheNames = Collections.emptySet();

	@Override
	public void afterPropertiesSet() {
		initializeCaches();
	}

	// @since 4.2.2  模版方法模式。   abstract方法loadCaches()交给子类实现~~~
	public void initializeCaches() {
		Collection<? extends Cache> caches = loadCaches();

		synchronized (this.cacheMap) {
			this.cacheNames = Collections.emptySet();
			this.cacheMap.clear();
			Set<String> cacheNames = new LinkedHashSet<>(caches.size());
			for (Cache cache : caches) {
				String name = cache.getName();
				// decorateCache是protected方法,交给子类  不复写也无所谓~~~
				this.cacheMap.put(name, decorateCache(cache));
				cacheNames.add(name);
			}
			// cacheNames是个只读视图~(框架设计中考虑读写特性~)
			this.cacheNames = Collections.unmodifiableSet(cacheNames);
		}
	}

	protected abstract Collection<? extends Cache> loadCaches();

	// 根据名称 获取Cache对象。若没有,就返回null
	@Override
	@Nullable
	public Cache getCache(String name) {
		Cache cache = this.cacheMap.get(name);
		if (cache != null) {
			return cache;
		} else {
			// Fully synchronize now for missing cache creation...
			synchronized (this.cacheMap) {
				cache = this.cacheMap.get(name);
				if (cache == null) {
					// getMissingCache默认直接返回null,交给子类复写~~~~
					// 将决定权交给实现者,你可以创建一个Cache,或者记录日志
					cache = getMissingCache(name);
					// cache  != null,主要靠getMissingCache这个方法了~~~向一个工厂一样创建一个新的~~~
					if (cache != null) {
						cache = decorateCache(cache);
						this.cacheMap.put(name, cache);
						// 向全局缓存里面再添加进去一个Cache~~~~
						updateCacheNames(name);
					}
				}
				return cache;
			}
		}
	}

	@Override
	public Collection<String> getCacheNames() {
		return this.cacheNames;
	}

	// @since 4.1
	protected final Cache lookupCache(String name) {
		return this.cacheMap.get(name);
	}
	...
}

这个抽象类其实还蛮重要的,它提供了基本的操作,如果已存的CacheManager们都无法满足你的要求,你可以自己通过继承AbstractCacheManager实现一个自己的CacheManager

比如Redis相关的RedisCacheManager就是继承自它的~

SimpleCacheManager
// @since 3.1
public class SimpleCacheManager extends AbstractCacheManager {
	private Collection<? extends Cache> caches = Collections.emptySet();
	public void setCaches(Collection<? extends Cache> caches) {
		this.caches = caches;
	}
	@Override
	protected Collection<? extends Cache> loadCaches() {
		return this.caches;
	}
}

最简单的一个CacheManager,它的caches都必须由调用者手动指定。若交给容器管理会自动执行afterPropertiesSet()方法,否则需要手动自己调用cacheManager.afterPropertiesSet();,自己放进去的setCaches才会生效~

NoOpCacheManager

一种基本的、无操作的CacheManager实现,适用于禁用缓存,通常用于在没有实际存储的情况下作为缓存声明。

ConcurrentMapCacheManager(重要)
// @since 3.1
public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {

	private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
	// true表示:若为null,就新创建一个缓存添加进去
	private boolean dynamic = true;
	// 是否允许value为null
	private boolean allowNullValues = true;
	// 指定此缓存管理器是存储每个条目的副本,还是存储其所有缓存的引用
	// 影响到ConcurrentMapCache对value值的存储~~~
	// false表示:存储它自己(引用)
	// true表示:存储一个副本(对序列化有要求~~~) 很少这么使用~~~
	private boolean storeByValue = false;
	

	public ConcurrentMapCacheManager() {
	}
	public ConcurrentMapCacheManager(String... cacheNames) {
		setCacheNames(Arrays.asList(cacheNames));
	}
	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;
		}
	}
	protected Cache createConcurrentMapCache(String name) {
		// isStoreByValue=true需要存储值的副本的时候,才对序列化有要求~~~否则直接存引用即可
		SerializationDelegate actualSerialization = (isStoreByValue() ? this.serialization : null);
		return new ConcurrentMapCache(name, new ConcurrentHashMap<>(256), isAllowNullValues(), actualSerialization);

	}

	@Override
	public void setBeanClassLoader(ClassLoader classLoader) {
		this.serialization = new SerializationDelegate(classLoader);
		if (isStoreByValue()) {
			// 重新创建Cache 因为要副本嘛
			recreateCaches();
		}
	}
	private void recreateCaches() {
		for (Map.Entry<String, Cache> entry : this.cacheMap.entrySet()) {
			entry.setValue(createConcurrentMapCache(entry.getKey()));
		}
	}

	@Override
	@Nullable
	public Cache getCache(String name) {
		Cache cache = this.cacheMap.get(name);
		// dynamic=true  为null的时候会动态创建一个
		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;
	}
	@Override
	public Collection<String> getCacheNames() {
		return Collections.unmodifiableSet(this.cacheMap.keySet());
	}
	...
}

它的缓存存储是基于内存的,所以它的生命周期是与应用关联的,对于生产级别的大型企业级应用程序,这可能并不是理想的选择,但它用于本地自己测试是个很好的选择。

CompositeCacheManager
// @since 3.1  
public class CompositeCacheManager implements CacheManager, InitializingBean {
	// 内部聚合管理着一批CacheManager
	private final List<CacheManager> cacheManagers = new ArrayList<>();
	// 若这个为true,则可以结合NoOpCacheManager实现效果~~~
	private boolean fallbackToNoOpCache = false;

	public CompositeCacheManager() {
	}
	public CompositeCacheManager(CacheManager... cacheManagers) {
		setCacheManagers(Arrays.asList(cacheManagers));
	}
	// 也可以调用此方法,来自己往里面添加(注意是添加)CacheManager们
	public void setCacheManagers(Collection<CacheManager> cacheManagers) {
		this.cacheManagers.addAll(cacheManagers);
	}

	public void setFallbackToNoOpCache(boolean fallbackToNoOpCache) {
		this.fallbackToNoOpCache = fallbackToNoOpCache;
	}

	// 如果fallbackToNoOpCache=true,那么在这个Bean初始化完成后,也就是在末尾添加一个NoOpCacheManager
	// 当然fallbackToNoOpCache默认值是false
	@Override
	public void afterPropertiesSet() {
		if (this.fallbackToNoOpCache) {
			this.cacheManagers.add(new NoOpCacheManager());
		}
	}


	// 找到一个cache不为null的就return了~
	@Override
	@Nullable
	public Cache getCache(String name) {
		for (CacheManager cacheManager : this.cacheManagers) {
			Cache cache = cacheManager.getCache(name);
			if (cache != null) {
				return cache;
			}
		}
		return null;
	}

	// 可以看到返回的是所有names,并且用的set
	@Override
	public Collection<String> getCacheNames() {
		Set<String> names = new LinkedHashSet<>();
		for (CacheManager manager : this.cacheManagers) {
			names.addAll(manager.getCacheNames());
		}
		return Collections.unmodifiableSet(names);
	}
}

CompositeCacheManager要通过一个或更多的缓存管理器来进行配置,它会迭代这些缓存管理器,以查找之前所缓存的值。

看完CacheManager,再看看Cache这个接口:

public interface Cache {
	String getName();
	// 返回本地存储的那个。比如ConcurrentMapCache本地就是用的一个ConcurrentMap
	Object getNativeCache();
	
	// 就是用下面的ValueWrapper把值包装了一下而已~
	@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);
	// @since 4.1
	// 不存在旧值直接put就先去了返回null,否则返回旧值(并且不会把新值put进去)
	@Nullable
	ValueWrapper putIfAbsent(Object key, @Nullable Object value);
	// 删除
	void evict(Object key);
	// 清空
	void clear();


	@FunctionalInterface
	interface ValueWrapper {
		@Nullable
		Object get();
	}
}

它的继承树非常简单,如下:
在这里插入图片描述

AbstractValueAdaptingCache
// @since 4.2.2 出现得还是挺晚的~~~
public abstract class AbstractValueAdaptingCache implements Cache {
	private final boolean allowNullValues;
	protected AbstractValueAdaptingCache(boolean allowNullValues) {
		this.allowNullValues = allowNullValues;
	}
	// lookup为抽象方法
	@Override
	@Nullable
	public ValueWrapper get(Object key) {
		Object value = lookup(key);
		return toValueWrapper(value);
	}
	@Nullable
	protected abstract Object lookup(Object key);

	// lookup出来的value继续交给fromStoreValue()处理~  其实就是对null值进行了处理
	// 若是null值就返回null,而不是具体的值了~~~
	@Override
	@SuppressWarnings("unchecked")
	@Nullable
	public <T> T get(Object key, @Nullable Class<T> type) {
		Object value = fromStoreValue(lookup(key));
		if (value != null && type != null && !type.isInstance(value)) {
			throw new IllegalStateException("Cached value is not of required type [" + type.getName() + "]: " + value);
		}
		return (T) value;
	}

	// 它是protected 方法  子类有复写
	@Nullable
	protected Object fromStoreValue(@Nullable Object storeValue) {
		if (this.allowNullValues && storeValue == NullValue.INSTANCE) {
			return null;
		}
		return storeValue;
	}

	// 提供给子类使用的方法,对null值进行转换~  子类有复写
	protected Object toStoreValue(@Nullable Object userValue) {
		if (userValue == null) {
			if (this.allowNullValues) {
				return NullValue.INSTANCE;
			}
			throw new IllegalArgumentException("Cache '" + getName() + "' is configured to not allow null values but null was provided");
		}
		return userValue;
	}

	// 把value进行了一层包装为SimpleValueWrapper
	@Nullable
	protected Cache.ValueWrapper toValueWrapper(@Nullable Object storeValue) {
		return (storeValue != null ? new SimpleValueWrapper(fromStoreValue(storeValue)) : null);
	}
}

显然该类是后来(Spring4.2.2)插入进来的专门对null值进行的处理。它提供了通用实现,来适配null值的问题。若你自定义Cache的实现,建议继承自此抽象类。

ConcurrentMapCache
public class ConcurrentMapCache extends AbstractValueAdaptingCache {
	private final String name;
	// 底层存储就是个java.util.concurrent.ConcurrentMap~~
	private final ConcurrentMap<Object, Object> store;
	...
	// 本处只写出这个最全的构造器  它是protected 的,其它的构造器是public的
	// 说白了serialization这个类不允许外部知道,子类知道即可~~~
	protected ConcurrentMapCache(String name, ConcurrentMap<Object, Object> store, boolean allowNullValues, @Nullable SerializationDelegate serialization) {

		super(allowNullValues);
		Assert.notNull(name, "Name must not be null");
		Assert.notNull(store, "Store must not be null");
		this.name = name;
		this.store = store;
		this.serialization = serialization;
	}

	// 显然只有指定了序列化器,才有可能true:存储副本
	public final boolean isStoreByValue() {
		return (this.serialization != null);
	}
	...
	@Override
	public final ConcurrentMap<Object, Object> getNativeCache() {
		return this.store;
	}
	// 这是父类的抽象方法
	@Override
	@Nullable
	protected Object lookup(Object key) {
		return this.store.get(key);
	}

	@Override
	@Nullable
	public <T> T get(Object key, Callable<T> valueLoader) {
		return (T) fromStoreValue(this.store.computeIfAbsent(key, r -> {
			try {
				return toStoreValue(valueLoader.call());
			} catch (Throwable ex) {
				throw new ValueRetrievalException(key, valueLoader, ex);
			}
		}));
	}

	@Override
	public void put(Object key, @Nullable Object value) {
		this.store.put(key, toStoreValue(value));
	}
	@Override
	@Nullable
	public ValueWrapper putIfAbsent(Object key, @Nullable Object value) {
		Object existing = this.store.putIfAbsent(key, toStoreValue(value));
		return toValueWrapper(existing);
	}
	@Override
	public void evict(Object key) {
		this.store.remove(key);
	}
	@Override
	public void clear() {
		this.store.clear();
	}
	...
}

ConcurrentMapCache它是spring-context提供的内建唯一缓存实现,它是完全基于本地内存的。

Springboot默认使用的是SimpleCacheConfiguration,它配置的是ConcurrentMapCacheManager来实现缓存,因此对应Cache实现为ConcurrentMapCache

NoOpCache

NoOpCache配合NoOpCacheManager使用~

使用示例

上面介绍了spring-context自带的一些缓存管理器CacheManager实现以及缓存Cache实现。接下来是骡子是马,现在拉出来遛遛,用个案例介绍它的使用方式:

    public static void main(String[] args) {
        CacheManager cacheManager = new ConcurrentMapCacheManager(); //使用ConcurrentMapCacheManager可以不用初始化指定,可以get的时候动态创建Cache
        //CacheManager cacheManager = new ConcurrentMapCacheManager("car");

		// 即使我们上面没有放进去名字为car的Cache,此处也会帮我们自动生成~~~
        Cache carCache = cacheManager.getCache("car");
        // 向缓存里加数据
        carCache.put("benz", "奔驰");
        carCache.put("bmw", "宝马");
        carCache.put("audi", "奥迪");

        System.out.println(carCache.getClass()); //class org.springframework.cache.concurrent.ConcurrentMapCache
        // 从缓存里获取数据 
        System.out.println(carCache.get("benz").get()); //奔驰
        System.out.println(carCache.get("benz", String.class)); //奔驰

    }

此处我们用ConcurrentMapCacheManager作为实例,其实还可以使用SimpleCacheManager这个最为简单的缓存管理器:

    public static void main(String[] args) {
        CacheManager cacheManager = new SimpleCacheManager();

        Cache car = cacheManager.getCache("car");
        System.out.println(car); //null
    }

可以看到若我们使用SimpleCacheManager这个缓存管理器,它并不会给我们动态生成Cache对象,一切都变成手动档了:

    public static void main(String[] args) {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        ConcurrentMapCache carMapCache = new ConcurrentMapCache("car");
        cacheManager.setCaches(Collections.singleton(carMapCache));
        cacheManager.afterPropertiesSet(); // 这一步是必须的

        Cache car = cacheManager.getCache("car");
        System.out.println(car); //org.springframework.cache.concurrent.ConcurrentMapCache@19469ea2
    }

我们手动准备Cache、手动调用afterPropertiesSet()才有用~

本例只介绍了单元测试时的使用方式,若和Spring集成,一切就更简单了,各位小伙伴自行实践吧~


CompositeCacheManager + NoOpCacheManager

CompositeCacheManager主要用于集合多个CacheManager实例,在使用多种缓存容器(比如Redis+EhCache的组合)时特别有用。

设想这一个场景:当代码中使用@Cacheable注解指定的cacheNames中,却没有这个cacheManagers时,执行时便会报错。但是若此时我们使用的是CompositeCacheManager并且设置fallbackToNoOpCache=true,那么它就会没找到也最终进入到NoOpCacheManager里面去(用NoOpCache代替~),此时就相当于禁用掉了缓存,而不抛出相应的异常。

当你的应用中使用到了多个缓存的时候,强烈建议使用CompositeCacheManager管理(当然倘若是一个缓存也可以使用它,方便日后更加方便的扩展,这点在缓存注解章节里有深入讲解~)


最后需要注意的是:如果需要让Spring容器中的缓存可以正常工作,必须配置至少一个CacheManager。

总结

本文介绍了JSR107的缓存抽象JCache的概念和设计,以及重点介绍了Spring对缓存的抽象,希望各位看官在实操过程中,也需要注重一定概念性东西,更需要关注一下业界规范。需要注意的是,缓存不是Java世界中的一个概念~~~

本文重在概念的介绍、核心接口类的理解。至于如何防止缓存穿透、缓存击穿、缓存雪崩和缓存刷新等高级话题,后面也会加以论述~

你可能感兴趣的:(#,享学Spring,MVC)