目录
- 1、Spring Cache 简介
- 2、常用注解
- 2.1、常用注解介绍
- 2.2、常用注解的主要参数
- 3、缓存注解上 SPEL 表达式可使用的元数据
- 4、入门案例
- 4.1、引入依赖
- 4.2、开启缓存功能
- 4.3、使用缓存
- 4.3.1、新建一个 `UserServiceImpl`
- 4.3.2、新建一个 `UserController`
- 5、工作原理
- 5.1、缓存自动配置类 CacheAutoConfiguration
- 5.2、默认缓存管理器 ConcurrentMapCacheManager
- 5.3、默认缓存 ConcurrentMapCache
- 5.4、@CachePut 注解
- 5.5、@Cacheable 注解
- 5.6、key 的生成策略
- 6、Spring Cache 集成其它第三方缓存
- 6.1、集成 Ehcache
- 6.1.1、引入依赖
- 6.1.2、开启缓存功能【已开启】
- 6.1.3、配置缓存管理器
- 6.1.4、调用 Controller
- 6.2、集成 Redis
- 6.2.1、引入依赖
- 6.2.2、添加配置
- 6.2.3、调用 Controller
1、Spring Cache 简介
Spring 从 3.1 开始定义了 org.springframework.cache.Cache
和 org.springframework.cache.CacheManager
接口来统一不同的缓存技术
Cache
:缓存接口,定义缓存操作。实现有:RedisCache
、EhCacheCache
、ConcurrentMapCache
等CacheManager
: 缓存管理器,管理各种缓存(Cache)组件keyGenerator
: 缓存数据时 key 生成策略serialize
: 缓存数据时 value 序列化策略
2、常用注解
2.1、常用注解介绍
cache 方面的注解主要有以下 5 个:
@Cacheable
【创建、查询缓存】:触发缓存入口(一般放在创建和获取的方法上,@Cacheable
注解会先查询是否已经有缓存。如果有,则直接从缓存中返回;如果没有,则会执行方法并返回结果缓存【返回方法返回 NULL,则不进行缓存】)@CachePut
【更新缓存】:更新缓存且不影响方法执行(用于修改的方法上,该注解下的方法始终会被执行)@CacheEvict
【删除缓存】:触发缓存的 eviction(用于删除的方法上)@Caching
【组合缓存配置】:将多个缓存组合在一个方法上(该注解可以允许一个方法同时设置多个注解)@CacheConfig
【类级别共享配置】:在类级别设置一些缓存相关的共同配置(与其它缓存配合使用),避免在每个缓存方法上重复配置相同的缓存属性
2.2、常用注解的主要参数
@Cacheable
/@CachePut
/@CacheEvict
注解上的主要参数:
参数名 | 解释 | 举例 |
---|---|---|
value | 缓存的名称,必须至少指定一个 | @Cacheable(value = “cache”) 或 @Cacheable(value = {“cache1”, “cache2”}) |
key | 缓存的 key。可以为空。如果指定,需要按照 SPEL 表达式编写;否则,按照方法的所有参数组合 | @Cacheable(value = “cache”, key = “#id”) |
condition | 缓存的条件,可以为空。使用 SPEL 表达式编写。只有为 true,才进行缓存/清除缓存;在调用方法之前、之后都能判断 | @Cacheable(value = “cache”, condition = “#userName.length() > 5”) |
allEntries | 是否清空所有缓存。如果指定为 true,则调用完方法后立即清空所有缓存 | |
beforeInvocation | 是否在方法执行前清空。如果指定为 true,则在方法执行前就清空缓存 | |
unless | 用于否决缓存,只在方法执行之后判断。对返回值 result 进行判断,如果为 true,则不会缓存;否则,会缓存 |
3、缓存注解上 SPEL 表达式可使用的元数据
Spring Cache 提供了一些供我们使用的 SpEL 上下文数据,下表:
名称 | 位置 | 描述 | 示例 |
---|---|---|---|
methodName | root 对象 | 当前被调用的方法名 | #root.methodname |
method | root 对象 | 当前被调用的方法 | #root.method.name |
target | root 对象 | 当前被调用的目标对象实例 | #root.target |
targetClass | root 对象 | 当前被调用的目标对象的类 | #root.targetClass |
args | root 对象 | 当前被调用的方法的参数列表 | #root.args[0] |
caches | root 对象 | 当前方法调用使用的缓存列表 | #root.caches[0].name |
Argument Name | 执行上下文 | 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数 | #artsian.id |
result | 执行上下文 | 方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false) | #result |
- 当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root对象的属性。如:
@Cacheable(key = "targetClass + methodName +#p0")
- 使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。如:
@Cacheable(value="userCache", key="#id")
、@Cacheable(value="userCache", key="#p0")
SpEL 提供了多种运算符
类型 | 运算符 |
---|---|
关系 | <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne |
算术 | +,- ,* ,/,%,^ |
逻辑 | &&, |
条件 | ?: (ternary),?: (elvis) |
正则表达式 | matches |
其他类型 | ?.,?[…],![…],1,$[…] |
4、入门案例
4.1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
4.2、开启缓存功能
开启缓存功能,需要先添加使能注解 @EnableCaching
,通常习惯在启动类配置,否则缓存注解@Cacheable
等不起作用
4.3、使用缓存
4.3.1、新建一个 UserServiceImpl
@Slf4j
@Service
@CacheConfig(cacheNames = "userCache")
public class UserServiceImpl {
// 模拟数据库数据
private Map<Integer, User> userMap = new HashMap<>();
@CachePut(key = "#user.id")
public User add(User user) {
log.info("add");
userMap.put(user.getId(), user);
return user;
}
@Cacheable(key = "#id")
public User get(Integer id) {
log.info("get");
return userMap.get(id);
}
@CachePut(key = "#user.id")
public User update(User user) {
log.info("update");
userMap.put(user.getId(), user);
return user;
}
@CacheEvict(key = "#id")
public void delete(Integer id) {
log.info("delete");
userMap.remove(id);
}
}
4.3.2、新建一个 UserController
@RestController
public class UserController {
@Autowired
private UserServiceImpl userServiceImpl;
@PostMapping
public String add(@RequestBody User user) {
userServiceImpl.add(user);
return "add";
}
@GetMapping("/{id}")
public User get(@PathVariable Integer id) {
User user = userServiceImpl.get(id);
return user;
}
@PutMapping
public String update(@RequestBody User user) {
userServiceImpl.update(user);
return "update";
}
@DeleteMapping("/{id}")
public String delete(@PathVariable Integer id) {
userServiceImpl.delete(id);
return "delete";
}
}
5、工作原理
5.1、缓存自动配置类 CacheAutoConfiguration
分析运行流程,首先要先从自动配置类入手,缓存的自动配置类为 CacheAutoConfiguration
,其中它在 @Import
中导入了 CacheAutoConfiguration.CacheConfigurationImportSelector.class
static class CacheConfigurationImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 获取所有的缓存类型
CacheType[] types = CacheType.values();
String[] imports = new String[types.length];
for(int i = 0; i < types.length; ++i) {
// 根据缓存类型获取对应的配置类
imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
}
return imports;
}
}
selectImports()
方法导入了所有的缓存配置类。因此,在返回值这里打上断点,查看结果如下:
那么,默认情况下是哪个配置类生效呢?在每一个配置类上都有对应的条件注解 @ConditionXxx()
。
以 org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
为例:
@ConditionalOnBean({Cache.class})
@ConditionalOnMissingBean({CacheManager.class})
@Conditional({CacheCondition.class})
class GenericCacheConfiguration {
//...
}
确保只有在存在 Cache
Bean 且不存在 CacheManager
Bean 的情况下,才会创建这个缓存管理器 SimpleCacheManager
为了在控制台上观察它的自动配置报告,现在 application.yml
全局配置文件中开启:
debug: true
运行主程序,打印控制台,发现只有一个 SimpleCacheConfiguration
匹配上。所以默认是它生效的:
而 SimpleCacheConfiguration
就是给容器中注册了一个 ConcurrentMapCacheManager
缓存管理器:
@ConditionalOnMissingBean({CacheManager.class})
@Conditional({CacheCondition.class})
class SimpleCacheConfiguration {
@Bean
ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers) {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return (ConcurrentMapCacheManager)cacheManagerCustomizers.customize(cacheManager);
}
}
5.2、默认缓存管理器 ConcurrentMapCacheManager
getCache()
方法:根据 name 获取缓存 ConcurrentMapCache
,如果为空,则创建;否则,直接返回
@Nullable
public Cache getCache(String name) {
Cache cache = (Cache)this.cacheMap.get(name);
if (cache == null && this.dynamic) {
synchronized(this.cacheMap) {
cache = (Cache)this.cacheMap.get(name);
if (cache == null) {
cache = this.createConcurrentMapCache(name);
this.cacheMap.put(name, cache);
}
}
}
return cache;
}
5.3、默认缓存 ConcurrentMapCache
public class ConcurrentMapCache extends AbstractValueAdaptingCache {
// 存数据
private final ConcurrentMap<Object, Object> store;
// 序列化
@Nullable
private final SerializationDelegate serialization;
// 根据 key 查找
@Nullable
protected Object lookup(Object key) {
return this.store.get(key);
}
// 添加
public void put(Object key, @Nullable Object value) {
this.store.put(key, this.toStoreValue(value));
}
// 删除
public void evict(Object key) {
this.store.remove(key);
}
//...
}
5.4、@CachePut 注解
@CachePut 注解流程:
- 先根据缓存名称获取缓存
Cache
【ConcurrentMapCacheManager
】,没有则创建。这里是ConcurrentMapCache
- 调用
put()
方法存入缓存中【ConcurrentMapCache
】,如果配置序列化,则将 value 先进行序列化
5.5、@Cacheable 注解
@Cacheable 注解流程:
- 先根据缓存名称获取缓存
Cache
【ConcurrentMapCacheManager
】,没有则创建。这里是ConcurrentMapCache
- 调用
lookup()
方法从缓存中查询【ConcurrentMapCache
】,如果查询到,则直接返回;否则,就调用目标方法,将目标方法返回的结果,放进缓存中
5.6、key 的生成策略
lookup()
方法的入参是 key。那这个 key 是如何来的呢?
通过断点信息查看,通过 CacheAspectSupport#generateKey()
方法生成的:
在它的方法中,是通过 keyGnerator
对象来制定 key 的生成策略:
默认情况是使用 SimpleKeyGenerator
,
通过生成的 key 查找 cache,如果存在,则直接返回:
6、Spring Cache 集成其它第三方缓存
SpringBoot的内置缓存以及整合第三方缓存
6.1、集成 Ehcache
6.1.1、引入依赖
只需添加以下依赖(在原有的依赖基础上):
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.9.3</version>
</dependency>
6.1.2、开启缓存功能【已开启】
6.1.3、配置缓存管理器
@Configuration
public class EhCacheConfig {
@Bean(name = "ehCacheManager")
public CacheManager cacheManager() {
// ①:设置内存存储位置和数量大小
ResourcePools resourcePools = ResourcePoolsBuilder.newResourcePoolsBuilder()
// 堆内存
.heap(1000)
// 堆外内存
.offheap(10, MemoryUnit.MB)
// 磁盘
.disk(20,MemoryUnit.MB, true)
.build();
// ②:设置生存时间
ExpiryPolicy expiry = ExpiryPolicyBuilder.timeToIdleExpiration(Duration.ofMillis(1000));
// ③:设置 CacheConfiguration
CacheConfiguration userCache = CacheConfigurationBuilder
.newCacheConfigurationBuilder(Integer.class, String.class, resourcePools)
.withExpiry(expiry)
.build();
// ④:设置磁盘存储的位置
CacheManagerBuilder<PersistentCacheManager> cacheManagerBuilder = CacheManagerBuilder.newCacheManagerBuilder().with(CacheManagerBuilder.persistence("D:/data/"));
// ⑤:设置缓存名称
cacheManagerBuilder = cacheManagerBuilder.withCache("userCache", userCache);
// 初始化 CacheManager
return cacheManagerBuilder.build(true);
}
}
6.1.4、调用 Controller
到这里,SpringBoot 整合 Ehcache 就做完了。可以发现一点,原始代码没有任何修改,仅仅是加了一组配置就可以变更缓存供应商了,这也是 SpringBoot 提供了统一的缓存操作接口的优势,变更实现并不影响原始代码的书写
6.2、集成 Redis
6.2.1、引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
6.2.2、添加配置
spring:
redis:
host: 127.0.0.1
port: 6379
cache:
redis:
# 是否使用 key 前缀
use-key-prefix: true
# key 前缀
key-prefix: "userCache:"
# 是否缓存空值
cache-null-values: true
# 缓存过期时间,单位毫秒
time-to-live: 3600000
6.2.3、调用 Controller
… ↩︎