关于spring的自定义缓存注解分析
作者:morris131 发布时间:2023-11-28 17:02:50
标签:spring,注解,自定义缓存
为什么要自定义缓存注解?
Spring Cache本身提供@Cacheable、@CacheEvict、@CachePut等缓存注解,为什么还要自定义缓存注解呢?
@Cacheabe不能设置缓存时间,导致生成的缓存始终在redis中,当然这一点可以通过修改RedisCacheManager的配置来设置缓存时间:
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration redisCacheConfiguration = getRedisCacheConfiguration();
RedisCacheManager cacheManager = RedisCacheManager
.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
.cacheDefaults(redisCacheConfiguration)
.build();
return cacheManager;
}
private RedisCacheConfiguration getRedisCacheConfiguration() {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
jackson2JsonRedisSerializer.setObjectMapper(om);
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
RedisSerializationContext
.SerializationPair
.fromSerializer(jackson2JsonRedisSerializer)
).entryTtl(Duration.ofDays(7)); // 设置全局key的有效事件为7天
return redisCacheConfiguration;
}
不过这个设置时全局的,所有的key的失效时间都一样,要想实现不同的key不同的失效时间,还得自定义缓存注解。
自定义缓存注解
Cached
类似Spring Cache的@Cacheable。
package com.morris.spring.custom.cache.annotation;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cached {
@AliasFor("cacheName")
String value() default "";
@AliasFor("value")
String cacheName() default "";
int expire() default 0;
TimeUnit expireUnit() default TimeUnit.SECONDS;
String key();
String condition() default "";
String unless() default "";
boolean sync() default false;
}
CacheUpdate
类似于Spring Cache的@CachePut。
package com.morris.spring.custom.cache.annotation;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CacheUpdate {
@AliasFor("cacheName")
String value() default "";
@AliasFor("value")
String cacheName() default "";
int expire() default 0;
TimeUnit expireUnit() default TimeUnit.SECONDS;
String key();
String condition() default "";
String unless() default "";
}
CacheInvalidate
类似于Spring Cache的@CacheEvict。
package com.morris.spring.custom.cache.annotation;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CacheInvalidate {
@AliasFor("cacheName")
String value() default "";
@AliasFor("value")
String cacheName() default "";
String key();
String condition() default "";
String unless() default "";
}
CachedAspect
@Cached注解的切面实现。
package com.morris.spring.custom.cache.annotation;
import com.morris.spring.custom.cache.Level2Cache;
import com.morris.spring.custom.cache.Level2CacheManage;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.cache.Cache;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.expression.EvaluationContext;
import javax.annotation.Resource;
import java.util.Objects;
@Configuration
@Aspect
@EnableAspectJAutoProxy // 开启AOP
public class CachedAspect {
@Resource
private Level2CacheManage cacheManager;
@Around(value = "@annotation(cached)")
public Object around(ProceedingJoinPoint joinPoint, Cached cached) throws Throwable {
Level2Cache cache = cacheManager.getCache(cached.cacheName());
ExpressionEvaluator evaluator = new ExpressionEvaluator();
EvaluationContext context = evaluator.createEvaluationContext(joinPoint);
Object key = evaluator.key(cached.key(), context);
if(cached.sync()) {
//
return cache.get(key, () -> {
try {
return joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
return null;
}
});
}
// 先查缓存
Cache.ValueWrapper valueWrapper = cache.get(key);
if (Objects.nonNull(valueWrapper)) {
return valueWrapper.get();
}
Object result = joinPoint.proceed();
context.setVariable("result", result);
Boolean condition = evaluator.condition(cached.condition(), context);
Boolean unless = evaluator.unless(cached.unless(), context);
if (condition && !unless) {
if (cached.expire() > 0) {
cache.put(key, result, cached.expire(), cached.expireUnit());
} else {
cache.put(key, result);
}
}
return result;
}
}
CacheUpdateAspect
@CacheUpdate注解的切面实现类。
package com.morris.spring.custom.cache.annotation;
import com.morris.spring.custom.cache.Level2Cache;
import com.morris.spring.custom.cache.Level2CacheManage;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.Configuration;
import org.springframework.expression.EvaluationContext;
import javax.annotation.Resource;
@Configuration
@Aspect
public class CacheUpdateAspect {
@Resource
private Level2CacheManage cacheManager;
@Around(value = "@annotation(cacheUpdate)")
public Object around(ProceedingJoinPoint joinPoint, CacheUpdate cacheUpdate) throws Throwable {
Level2Cache cache = cacheManager.getCache(cacheUpdate.cacheName());
ExpressionEvaluator evaluator = new ExpressionEvaluator();
EvaluationContext context = evaluator.createEvaluationContext(joinPoint);
Object key = evaluator.key(cacheUpdate.key(), context);
Object result = joinPoint.proceed();
context.setVariable("result", result);
Boolean condition = evaluator.condition(cacheUpdate.condition(), context);
Boolean unless = evaluator.unless(cacheUpdate.unless(), context);
if (condition && !unless) {
if (cacheUpdate.expire() > 0) {
cache.put(key, result, cacheUpdate.expire(), cacheUpdate.expireUnit());
} else {
cache.put(key, result);
}
}
return result;
}
}
CacheInvalidateAspect
@CacheInvalidate注解的切面实现类。
package com.morris.spring.custom.cache.annotation;
import com.morris.spring.custom.cache.Level2Cache;
import com.morris.spring.custom.cache.Level2CacheManage;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.Configuration;
import org.springframework.expression.EvaluationContext;
import javax.annotation.Resource;
@Configuration
@Aspect
public class CacheInvalidateAspect {
@Resource
private Level2CacheManage cacheManager;
@Around(value = "@annotation(cacheInvalidate)")
public Object around(ProceedingJoinPoint joinPoint, CacheInvalidate cacheInvalidate) throws Throwable {
Level2Cache cache = cacheManager.getCache(cacheInvalidate.cacheName());
ExpressionEvaluator evaluator = new ExpressionEvaluator();
EvaluationContext context = evaluator.createEvaluationContext(joinPoint);
Object key = evaluator.key(cacheInvalidate.key(), context);
Object result = joinPoint.proceed();
context.setVariable("result", result);
Boolean condition = evaluator.condition(cacheInvalidate.condition(), context);
Boolean unless = evaluator.unless(cacheInvalidate.unless(), context);
if (condition && !unless) {
cache.evict(key);
}
return result;
}
}
ExpressionEvaluator
ExpressionEvaluator主要用于解析注解中key、condition、unless属性中的表达式。
package com.morris.spring.custom.cache.annotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.util.StringUtils;
import java.lang.reflect.Method;
public class ExpressionEvaluator {
private final SpelExpressionParser parser = new SpelExpressionParser();
public EvaluationContext createEvaluationContext(ProceedingJoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method specificMethod = AopUtils.getMostSpecificMethod(methodSignature.getMethod(), joinPoint.getTarget().getClass());
MethodBasedEvaluationContext context = new MethodBasedEvaluationContext(joinPoint.getTarget(),
specificMethod, joinPoint.getArgs(), new DefaultParameterNameDiscoverer());
return context;
}
public Object key(String keyExpression, EvaluationContext evalContext) {
Expression expression = parser.parseExpression(keyExpression);
return expression.getValue(evalContext);
}
public boolean condition(String conditionExpression, EvaluationContext evalContext) {
if(!StringUtils.hasText(conditionExpression)) {
return true;
}
Expression expression = parser.parseExpression(conditionExpression);
return (Boolean.TRUE.equals(expression.getValue(evalContext, Boolean.class)));
}
public boolean unless(String unlessExpression, EvaluationContext evalContext) {
if(StringUtils.hasText(unlessExpression)) {
return false;
}
Expression expression = parser.parseExpression(unlessExpression);
return (Boolean.TRUE.equals(expression.getValue(evalContext, Boolean.class)));
}
}
总结
自定义缓存注解使用了AOP的通知功能,所以需要开启AOP,需要在配置类上加上@EnableAspectJAutoProxy注解。
改进:可使用类似@EnableCache注解导入一个入口类,不再需要多个切面,多个注解的处理逻辑放在一起,参考Spring Cache。
扩展:解析表达式可使用缓存,加快解析速度;方法上面支持多个@Cached注解。
来源:https://morris131.blog.csdn.net/article/details/124426114
0
投稿
猜你喜欢
- 本文实例为大家分享了java实现鲜花销售系统的具体代码,供大家参考,具体内容如下一、练习目标1.体会数组的作用2.找到分层开发的感觉3.收获
- 最近在做项目开始,涉及到服务器与安卓之间的接口开发,在此开发过程中发现了安卓与一般浏览器不同,安卓在每次发送请求的时候并不会带上上一次请求的
- 昨天写了一篇Redis布隆过滤器相关的命令的文章,今天来说一说springboot中如何简单在代码中使用布隆过滤器吧。目前市面上也有好几种实
- 实现“摇一摇”功能,其实很简单,就是检测手机的重力感应,具体实现代码如下:1、在 AndroidManifest.xml 中添加操作权限2、
- 1. 简单说明嗨,大家好!今天给大家分享的是Mybatis-plus 插件的分页机制,说起分页机制,相信我们程序员都不陌生,今天,我就给大家
- 一、Java内存区域方法区(公有):用户存储已被虚拟机加载的类信息,常量,静态常量,即时编译器编译后的代码等数据。异常状态 OutOfMem
- Linux Hadoop 2.7.3 安装搭建Hadoop实现了一个分布式文件系统(Hadoop Distributed File Syst
- 获取自定义菜单查询返回的结果有乱码解决方法:string Posturl = "https://api.weixin.qq.com
- Android 无障碍的全局悬浮窗可以在屏幕上添加 UI 供用户进行快捷操作,可以展示在所有应用程序之上长期展示。另一方面,在一些自动化场景
- spring mvc中的@PathVariable是用来获得请求url中的动态参数的,十分方便,复习下: @Controller publ
- 原理是使用LinkedHashMap来实现,当缓存超过大小时,将会删除最老的一个元组。实现代码如下所示import java.util.Li
- Java常用API介绍API概念什么是API?API(Application Programming interface) 应用程序编程接口
- Java 判断字符串中是否包含中文的实例详解 Java判断一个字符串是否有中文是利用Unicode编码来判断,因为中
- 本文实例为大家分享了Java实现扑克牌程序的具体代码,供大家参考,具体内容如下思路:在实现之前,先要想好步骤,思路清晰才不会出错。要实现一副
- 1. 定义在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。2. 使用的
- C语言/C++怎样产生随机数:这里要用到的是rand()函数, srand()函数,和time()函数。需要说明的是,iostream头文件
- fopen(打开文件)相关函数 open,fclose表头文件 #include<stdio.h>定义函数 FILE * fop
- 一、Optional类的来源到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公
- 初次接触spring-boot的时候,我们经常会看到这样的文章:“
- 本文实例为大家分享了C# GDI+实现时钟表盘的具体代码,供大家参考,具体内容如下一、设计如下图界面按键“打开时钟&am