前言
缓存(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.cache</groupId> <artifactId>cache-api</artifactId> <version>1.1.1</version> </dependency>
2019年5月发布的最新的1.1.1版本。1.0.0版本是2014年5月发布的
从这个jar的类里可以看到,它几乎所有都是接口,自己并不提供具体实现(第三方厂商自行实现)。
JCache的实现产品挺少的,
Ehcache3.x有实现JSR107相关规范接口
它的核心类的层次结构图:
- 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-context里面的,作为上下文的核心内容,并不需要额外导包。
Spring的缓存抽象相关类的层次结构非常简单:
- CacheManager:缓存管理器。管理各种缓存(Cache)组件
- Cache:为缓存的组件规范定义,包含缓存的各种操作集合。比如它有很多实现:ConcurrentMapCache、RedisCache、EhCacheCache(额外导包)
说明:看到这个层次结构,很多小伙伴会问为何没有Expire的定义?
这里我想说:这也是我比较费解的地方之一。Expire作为缓存非常重要的能力,为何不抽象出来呢???这也是我们经常苦恼的地方:@Cacheable注解竟然不支持TTL过期时间的设置,着实让人很蛋疼~~~
我个人把Spring没有Expire这个理解为Spring缓存抽象的一个功能缺失,说不客气点就是Spring的一个Bug。不知各位对此是否和我相同意见呢?欢迎砸我讨论
至于为何它一直都没有“修复”?我感觉是因为Cache属于它对外公布的API,各大产品都自己实现了Expire,而且方式不尽相同,所以最终它想统一就很难了,很难做到最好的兼容性~
CacheManager和Cache的使用示例
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内置的最基础的缓存管理器类。
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就是继承自它的~






