spring集成cache支持对缓存进行处理,spring cache支持多种缓存实现,本文对缓存实现方案中的redis操作进行说明,期间会对应源码进行解读.如果对源码不感兴趣的同学可以忽略,仅关注具体使用即可.
1.案例demo
1.1 需要添加依赖
1.2 redis配置文件
1.3 启动类需要添加的注解@EnableCaching
1.4 业务代码
2.常用注解(@Cacheable、@CachePut、CacheEvict)使用说明以及注解属性源码解读
3.注意事项
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
@EnableCaching
@Configuration
public class RedisConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheManager redisCacheManager = new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
// 设置缓存的有效时间,单位秒
this.getRedisCacheConfigurationWithTtl(600),
// 指定 key 策略
this.getRedisCacheConfigurationMap()
);
redisCacheManager.setTransactionAware(true);
return redisCacheManager;
}
private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>(16);
return redisCacheConfigurationMap;
}
private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
// 指定使用GenericJackson2JsonRedisSerializer序列化方式
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
RedisSerializationContext
.SerializationPair
.fromSerializer(genericJackson2JsonRedisSerializer)
).entryTtl(Duration.ofSeconds(seconds));
return redisCacheConfiguration;
}
}
@EnableCaching
@SpringBootApplication
@MapperScan("com.chenghao.program.chenghaoprogram")
public class ChenghaoProgramApplication {
public static void main(String[] args) {
ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(ChenghaoProgramApplication.class, args);
}
}
@RequestMapping("/redis")
@RestController
public class TestSpringCacheRedisController {
@Autowired
private TestSpringCacheRedisServiceImp testSpringCacheRedisService;
@GetMapping("/findPersonList")
public void findPersonList(){
List<Person> personList = testSpringCacheRedisService.findPersonList();
System.out.println(personList);
}
@GetMapping("/findPersonInfo")
public void findPersonInfo(String name){
Person personInfo = testSpringCacheRedisService.findPersonInfo(name);
System.out.println(personInfo);
}
@PostMapping
public void updatePersonalInfo(String name){
testSpringCacheRedisService.updatePersonalInfo(name);
System.out.println("修改完成");
}
@DeleteMapping
public void deletePersonalInfo(String name){
testSpringCacheRedisService.deletePersonalInfo(name);
System.out.println("删除完成");
}
}
逻辑层:
public interface TestSpringCacheRedisService {
// 查询人员信息
List<Person> findPersonList();
// 根据姓名查询用户信息
Person findPersonInfo(String name);
// 更新用户信息
Person updatePersonalInfo(String name);
// 删除用户信息
void deletePersonalInfo(String name);
}
@Service
public class TestSpringCacheRedisServiceImp implements TestSpringCacheRedisService {
@Cacheable(value = "personList",key = "#root.methodName",unless = "#result.size() == 3")
@Override
public List<Person> findPersonList() {
int a=0;
List<Person> PersonList = Arrays.asList(new Person(13, "张三"),
new Person(14, "李四"));
return PersonList;
}
// key定义方式:"#参数名"或者"#p参数index"
@Cacheable(value = "personalInfo",key = "#name",condition = "#a0 != \"王五\"")
@Override
public Person findPersonInfo(String name) {
int a=0;
// 模拟数据库获取数据
Person person=null;
if("张三".equals(name)){
person = new Person(13, "张三");
}else if("李四".equals(name)){
person = new Person(14, "李四");
}else if("王五".equals(name)){
person = new Person(15, "王五");
}
return person;
}
@CachePut(value = "personalInfo",key = "#name")
@Override
public Person updatePersonalInfo(String name) {
int a=0;
// 模拟数据库修改数据,修改张三的年龄
Person person=null;
if("张三".equals(name)){
person = new Person(23, "张三");
}
// 主要要将修改后的对象信息进行返回,否则会将之前的缓存value信息置为null
return person;
}
@CacheEvict(value = "personalInfo",key = "#name",beforeInvocation = true)
@Override
public void deletePersonalInfo(String name) {
System.out.println("模拟执行数据库删除操作");
int a=1/0;
}
}
实体类:
public class Person implements Serializable {
private int age;
private String name;
public Person() {
}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
// 省略get/set以及tostring
}
2.1三个注解属性对照表
@Caching为三个注解的组合,在此不做讲解
2.2常用的spel表达式(很多属性支持spel表达式):
#root.method 或#root.methodName:获取当前请求方法或方法名;root对应CacheExpressionRootObject;
#root.target或#root.targetClass: 获取当前方法请求目标对象或是目标class对象;
#root.caches: 获取当缓存cache列表.
#root.args[1]或#p1 或#a1:获取请求方法中参数信息。
下面对各个属性进行介绍:
2.3 cacheNames和value作用:标识缓存cache的命名空间,即缓存的名称。value是CacheNames的别名,两者作用相同。
2.4 key作用:指定缓存数据的key值,默认会按照key生成策略生成,支持spel表达式。
demo中方法缓存的的key值说明:
// 缓存的key值为findPersonList,value为PersonList集合
@Cacheable(value = "personList",key = "#root.methodName",unless = "#result.size() == 3")
@Override
public List<Person> findPersonList() {
List<Person> PersonList = Arrays.asList(new Person(13, "张三"),
new Person(14, "李四"));
return PersonList;
}
// 缓存的key值为传入name的实参,value为person对象
@Cacheable(value = "personalInfo",key = "#name",condition = "#a0 != \"王五\"")
@Override
public Person findPersonInfo(String name) {
// 模拟数据库获取数据
Person person=null;
if("张三".equals(name)){
person = new Person(13, "张三");
}else if("李四".equals(name)){
person = new Person(14, "李四");
}else if("王五".equals(name)){
person = new Person(15, "王五");
}
return person;
}
对应源码:
CacheAspectSupport.java中generateKey
protected Object generateKey(@Nullable Object result) {
// 如果key属性中指定spel表达式,则按照解析后的spel表达式生成key,否则按照默认的策略进行生成
if (StringUtils.hasText(this.metadata.operation.getKey())) {
EvaluationContext evaluationContext = this.createEvaluationContext(result);
return CacheAspectSupport.this.evaluator.key(this.metadata.operation.getKey(), this.metadata.methodKey, evaluationContext);
} else {
return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
}
}
2.5 keyGenerator作用:缓存数据key的生成器。有两种实现
cacheManager作用:配置cacheManager,可指定序列化方式和缓存ttl有效期等配置信息.
cacheResolver作用:注解解析会初始化CacheOperationContext,其中caches为当前的缓存列表信息,就是通过cacheResolver进行获取.。CacheResolver常见的实现类如下:
对应源码(
CacheAspectSupport.java中CacheOperationContext
):
public CacheOperationContext(CacheOperationMetadata metadata, Object[] args, Object target) {
this.metadata = metadata;
this.args = extractArgs(metadata.method, args);
this.target = target;
// 缓存列表caches获取
this.caches = CacheAspectSupport.this.getCaches(this, metadata.cacheResolver);
this.cacheNames = createCacheNames(this.caches);
}
AbstractCacheResolver.java中resolveCaches
:
public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
Collection<String> cacheNames = getCacheNames(context);
if (cacheNames == null) {
return Collections.emptyList();
}
Collection<Cache> result = new ArrayList<>(cacheNames.size());
for (String cacheName : cacheNames) {
Cache cache = getCacheManager().getCache(cacheName);
if (cache == null) {
throw new IllegalArgumentException("Cannot find cache named '" +
cacheName + "' for " + context.getOperation());
}
result.add(cache);
}
return result;
}
2.6 condition作用:将符合条件的数据存入缓存或是删除缓存,不指定时,全部缓存数据都进行存入或清空.支持的spel表达式;
// condition = "#a0 != \"王五\"" 标识如果name为王五的数据不存入缓存中
@Cacheable(value = "personalInfo",key = "#name",condition = "#a0 != \"王五\"")
@Override
public Person findPersonInfo(String name) {
// 模拟数据库获取数据
Person person=null;
if("张三".equals(name)){
person = new Person(13, "张三");
}else if("李四".equals(name)){
person = new Person(14, "李四");
}else if("王五".equals(name)){
person = new Person(15, "王五");
}
return person;
}
对应源码解读(
CacheAspectSupport.java
):
private void collectPutRequests(Collection<CacheOperationContext> contexts,
@Nullable Object result, Collection<CachePutRequest> putRequests) {
for (CacheOperationContext context : contexts) {
// 解析注解中condition标识的条件是否成立,如果成立或是condition中没有指定条件,则将该方法请求存入putRequests中,为下一步添加到缓存中做准备.
if (isConditionPassing(context, result)) {
Object key = generateKey(context, result);
putRequests.add(new CachePutRequest(context, key));
}
}
}
protected boolean isConditionPassing(@Nullable Object result) {
// 每次请求会判断注解中是否有condition属性,如果有则按照spel表达式进行解析,判断条件是否成立.
if (this.conditionPassing == null) {
if (StringUtils.hasText(this.metadata.operation.getCondition())) {
EvaluationContext evaluationContext = createEvaluationContext(result);
this.conditionPassing = evaluator.condition(this.metadata.operation.getCondition(),
this.metadata.methodKey, evaluationContext);
}
else {
this.conditionPassing = true;
}
}
return this.conditionPassing;
}
2.7 unless作用:与condition作用相反,unless表达式成立时不进行缓存;默认为空,为空时表示进行缓存.
// unless = "#result.size() == 3" 表示方法执行结果集合个数为3时不进行缓存.
@Cacheable(value = "personList",key = "#root.methodName",unless = "#result.size() == 3")
@Override
public List<Person> findPersonList() {
List<Person> PersonList = Arrays.asList(new Person(13, "张三"),
new Person(14, "李四"));
return PersonList;
}
对应源码解析(
CacheAspectSupport.java
):
// 判断返回结果是否需要添加到缓存中
protected boolean canPutToCache(@Nullable Object value) {
String unless = "";
if (this.metadata.operation instanceof CacheableOperation) {
unless = ((CacheableOperation) this.metadata.operation).getUnless();
}
else if (this.metadata.operation instanceof CachePutOperation) {
unless = ((CachePutOperation) this.metadata.operation).getUnless();
}
// 注解中unless属性中表达式不为空则j进行spel表达式解析
if (StringUtils.hasText(unless)) {
EvaluationContext evaluationContext = createEvaluationContext(value);
return !evaluator.unless(unless, this.metadata.methodKey, evaluationContext);
}
return true;
}
解析表达式判断条件是否存成立最底层逻辑:
public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException {
// 表达式左边内容解析
Object left = this.getLeftOperand().getValueInternal(state).getValue();
// 表达式右边内容解析
Object right = this.getRightOperand().getValueInternal(state).getValue();
this.leftActualDescriptor = CodeFlow.toDescriptorFromObject(left);
this.rightActualDescriptor = CodeFlow.toDescriptorFromObject(right);
// 判断条件是否存成立
return BooleanTypedValue.forValue(equalityCheck(state.getEvaluationContext(), left, right));
}
2.8 sync作用:如果有多个线程正在运行,开启时会为同一个键加载值。默认为false,同步操作有几个限制:不能与unless同时使用;只能指定一个缓存,不能组合其他与缓存相关的操作.
2.9 allEntries作用:是否删除所有缓存信息,默认false,如果设置为true,key属性不能有值,否则只会清空key对应的缓存非清空所有缓存.
对应源码解析(
SpringCacheAnnotationParser.java中parseEvictAnnotation
):
private CacheEvictOperation parseEvictAnnotation(
AnnotatedElement ae, DefaultCacheConfig defaultConfig, CacheEvict cacheEvict) {
CacheEvictOperation.Builder builder = new CacheEvictOperation.Builder();
// 省略部分代码,解析allEntries属性值,赋值给CacheWide
builder.setCacheWide(cacheEvict.allEntries());
return op;
}
执行清除缓存逻辑(
CacheAspectSupport.java中performCacheEvict
):
private void performCacheEvict(
CacheOperationContext context, CacheEvictOperation operation, @Nullable Object result) {
Object key = null;
for (Cache cache : context.getCaches()) {
// 判断@CacheEvict中allEntries是否设置为true,如果是则删除所有,CacheWid对应allEntries,为TRUE,执行doClear为清空所有缓存。
if (operation.isCacheWide()) {
logInvalidating(context, operation, null);
doClear(cache, operation.isBeforeInvocation());
}
else {
if (key == null) {
key = generateKey(context, result);
}
logInvalidating(context, operation, key);
doEvict(cache, key, operation.isBeforeInvocation());
}
}
}
2.91 beforeInvocation作用:发送请求,调用方法执行之前进行删除缓存操作,不论后续业务处理是否成功都删除缓存数据,默认false;适用场景:执行删除业务逻辑成功,但由于其他业务处理异常导致缓存删除失败;
// beforeInvocation = true表示方法执行deletePersonalInfo就进行删除缓存数据,即便具体业务中抛出异常缓存也被清空。spring cache底层是根据aop进行对方法前后进行业务增强。先删除缓存,然后代理对象执行方法,不论代理对象执行方法逻辑是否成功,缓存都已被删除。
@CacheEvict(value = "personalInfo",key = "#name",beforeInvocation = true)
@Override
public void deletePersonalInfo(String name) {
System.out.println("模拟执行数据库删除操作");
int a=1/0;
}
对应源码(
CacheAspectSupport.java中execute
):
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
// 省略部分代码
// 执行删除缓存操作,仅当方法标注 @CacheEvict,并且beforeInvocation为true时生效
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
// 省略部分代码
//代理对象执行具体的业务逻辑,如果有异常抛出,以后的逻辑不会进行
returnValue = invokeOperation(invoker);
cacheValue = unwrapReturnValue(returnValue);
// @CachePut注解处理逻辑添加缓存处理
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
// 缓存请求结果,最终是调用ConcurrentMapCache中put方法将查询缓存数据存入内存ConcurrentMapCache中
for (CachePutRequest cachePutRequest : cachePutRequests) {
cachePutRequest.apply(cacheValue);
}
// @CacheEvict注解处理逻辑对缓存进行删除操作
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
return returnValue;
}
@CachePut注解属性同@Cacheable,底层实现都是添加缓存,注意@CachePut使用时需要将修改后对象信息进行返回(修改的方法中要将修改后的要缓存的结果返回),否则会将已经存在的缓存的value置为null.
@CachePut(value = "personalInfo",key = "#name")
@Override
public Person updatePersonalInfo(String name) {
int a=0;
// 模拟数据库修改数据,修改张三的年龄
Person person=null;
if("张三".equals(name)){
person = new Person(23, "张三");
}
// 主要要将修改后的对象信息进行返回,否则会将之前的缓存value信息置为null
return person;
}
对spring cache 完整执行流程相关源码感兴趣的点击:
通俗易懂讲解Spring Cache基于ConcurrentMapCache源码实现原理
redis序列化处理:
springboot集成redis序列化问题汇总
原创不易,欢迎点赞收藏加评论!