赞
踩
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(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序列化问题汇总
原创不易,欢迎点赞收藏加评论!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。