Spring Boot与缓存
1、Spring Boot与缓存
缓存应该是每个系统都应考虑的功能,用来加速系统的访问以及提升系统的性能;
引入缓存中间件,将高频信息从数据库存入缓存,从缓存拿数据;如果缓存中没有,在到数据库中查找吗,继续放入缓存。应用程序和缓存的交互式非常快的。
还有验证码这些临时性数据就没有必要存在数据库中了,缓存是不错的选择。
那么为了统一缓存的开发规范以及提升系统的扩展性,J2EE发布了JSR-107规范,但由于整合难度大,用的比较少。JSR-107规范定义的都是一些接口,这样的好处就类似JDBC一样,面向接口编程。但市面上的组件不都提供JSR-107的实现。但为了简化开发,Spring更多使用的是自己的Spring缓存抽象。
1.1 JSR107
Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry 和 Expiry。
CachingProvider定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider。
CacheManager定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。
Cache是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个CacheManager所拥有。
Entry是一个存储在Cache中的key-value对。
Expiry 每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。
1.2 Spring缓存抽象
Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术;
并支持使用JCache(JSR-107)注解简化我们开发;
Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache等;
每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
使用Spring缓存抽象时我们需要关注以下两点;
- 确定方法需要被缓存以及他们的缓存策略
- 从缓存中读取之前缓存存储的数据
1.3 几个重要概念&缓存注解
Cache | 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等 |
---|---|
CacheManager | 缓存管理器,管理各种缓存(Cache)组件 |
@Cacheable | 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 |
@CacheEvict | 清空缓存 |
@CachePut | 保证方法被调用,又希望结果被缓存。 |
@EnableCaching | 开启基于注解的缓存 |
keyGenerator | 缓存数据时key生成策略 |
serialize | 缓存数据时value序列化策略 |
都是作用于缓存,不作用于数据库?
1.4 搭建基本环境
- 创建出department和employee表
- 创建javaBean封装数据
- 整合MyBatis操作数据库
- 配置数据源信息
- 使用注解版的MyBatis
- @MapperScan指定需要扫描的mapper接口所在的包
2、快速体验缓存
缓存SpEL可用元数据
名字 | 位置 | 描述 | 示例 |
---|---|---|---|
methodName | root object | 当前被调用的方法名 | #root.methodName |
method | root object | 当前被调用的方法 | #root.method.name |
target | root object | 当前被调用的目标对象 | #root.target |
targetClass | root object | 当前被调用的目标对象类 | #root.targetClass |
args | root object | 当前被调用的方法的参数列表 | #root.args[0] |
caches | root object | 当前方法调用使用的缓存列表(如@Cacheable(value={“cache1”, “cache2”})),则有两个cache | #root.caches[0].name |
argument name | evaluation context | 方法参数的名字. 可以直接 #参数名 ,也可以使用 #p0或#a0 的形式,0代表参数的索引; | #iban 、 #a0 、 #p0 |
result | evaluation context | 方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’,’cache put’的表达式 ’cache evict’的表达式beforeInvocation=false) | #result |
步骤:
2.1 开启基于注解的缓存 @EnableCaching
1 | // 启动类 |
2.2 标注缓存注解即可
将方法的运行结果进行缓存;以后再要相同的数据,直接从缓存中获取,不用调用方法;
CacheManager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字;
2.3 @Cacheable
1 | // DepartmentController.java |
2.3.1 几个属性:
@Cacheable的属性 | 详情 | 用法 |
---|---|---|
cacheNames/value | 指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存 | |
key | 缓存数据使用的key;可以用它来指定。默认是使用方法参数的值 1-方法的返回值 | 编写SpEL; #i d;参数id的值 #a0 #p0 #root.args[0] getEmp[2] |
keyGenerator | key的生成器;可以自己指定key的生成器的组件id | key/keyGenerator:二选一使用 |
cacheManager | 指定缓存管理器;或者cacheResolver指定获取解析器 | |
condition | 指定符合条件的情况下才缓存; | condition = “#id>0” condition = “#a0>1”:第一个参数的值>1的时候才进行缓存 |
unless | 否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断 | unless = “#result == null” unless = “#a0==2”:如果第一个参数的值是2,结果不缓存 |
sync | 是否使用异步模式 |
2.3.2 应用举例
1 | // DepartmentService.java |
2.3.3 运行主程序
访问http://localhost:8088/service/dept/1,查看控制台,然后在刷新浏览器再查看控制台,发现后台并不执行数据库查询:
2.3.4 分析缓存的工作原理以及工作步骤
结合以上例子来:
自动配置类入手:
CacheAutoConfiguration.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
.class}) ({CacheManager
@ConditionalOnBean({CacheAspectSupport.class})
@ConditionalOnMissingBean(
value = {CacheManager.class},
name = {"cacheResolver"}
)
.class}) ({CacheProperties
@AutoConfigureBefore({HibernateJpaAutoConfiguration.class})
@AutoConfigureAfter({
CouchbaseAutoConfiguration.class,
HazelcastAutoConfiguration.class,
RedisAutoConfiguration.class
})
@Import({CacheAutoConfiguration.CacheConfigurationImportSelector.class})
public class CacheAutoConfiguration {
static final String VALIDATOR_BEAN_NAME = "cacheAutoConfigurationValidator";
public CacheAutoConfiguration() {
}
public CacheManagerCustomizers cacheManagerCustomizers(
ObjectProvider<List<CacheManagerCustomizer<?>>> customizers
) {
return new CacheManagerCustomizers((List)customizers.getIfAvailable());
}
...
static class CacheConfigurationImportSelector implements ImportSelector {
CacheConfigurationImportSelector() {
}
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;
}
}
}放了一些缓存管理的自定义器(
CacheManagerCustomizers
)等;关注一下
@Import({CacheAutoConfiguration.CacheConfigurationImportSelector.class})
,查看源码来到上面代码中的CacheConfigurationImportSelector
方法;在方法体中调用的selectImports
打上断点,debug来查看给容器中导入了哪些缓存组件:缓存的配置类哪个配置类默认生效?配置文件加入以下配置,然后运行主程序
1
2# 打开自动配置报告
debug=true可以看到控制台:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29=========================
AUTO-CONFIGURATION REPORT
=========================
Positive matches:
-----------------
SimpleCacheConfiguration matched:
- Cache org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration
automatic cache type (CacheCondition)
- @ConditionalOnMissingBean (types: org.springframework.cache.CacheManager;
SearchStrategy: all) did not find any beans (OnBeanCondition)
...
Negative matches:
-----------------
ActiveMQAutoConfiguration:
Did not match:
- @ConditionalOnClass
did not find required classes 'javax.jms.ConnectionFactory',
'org.apache.activemq.ActiveMQConnectionFactory' (OnClassCondition)
AopAutoConfiguration.CglibAutoProxyConfiguration:
Did not match:
- @ConditionalOnProperty (spring.aop.proxy-target-class=true)
did not find property 'proxy-target-class' (OnPropertyCondition)
...可以看出是
SimpleCacheConfiguration
配置类生效;给容器中注册了一个CacheManager
:ConcurrentMapCacheManager
可以获取和创建ConcurrentMapCache类型的缓存组件;其的作用将数据保存在ConcurrentMap中;
2.3.5 @Cacheable运行流程
方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;
(CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。
去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key;
SimpleKeyGenerator生成key的默认策略;
- 如果没有参数;key=new SimpleKey();
- 如果有一个参数:key=参数的值
- 如果有多个参数:key=new SimpleKey(params);
没有查到缓存就调用目标方法;
- 将目标方法返回的结果,放进缓存中
@Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,如果没有就运行方法并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据。
核心:
- 使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
- key使用keyGenerator生成的,默认是SimpleKeyGenerator
2.4 @CacheEvict
2.5 @CachePut
默认使用的是ConcurrentMapCacheManager==ConcurrentMapCache;将数据保存在 ConcurrentMap
开发中使用缓存中间件;redis、memcached、ehcache;
三、整合redis作为缓存
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。
1、安装redis:使用docker;
2、引入redis的starter
3、配置redis
4、测试缓存
原理:CacheManager===Cache 缓存组件来实际给缓存中存取数据
1)、引入redis的starter,容器中保存的是 RedisCacheManager;
2)、RedisCacheManager 帮我们创建 RedisCache 来作为缓存组件;RedisCache通过操作redis缓存数据的
3)、默认保存数据 k-v 都是Object;利用序列化保存;如何保存为json
1、引入了redis的starter,cacheManager变为 RedisCacheManager;
2、默认创建的 RedisCacheManager 操作redis的时候使用的是 RedisTemplate
3、RedisTemplate
4)、自定义CacheManager;