利用AOP自定义Redis缓存注解

tech2023-02-09  92

背景

在查询类开发中我们有使用缓存的场景,一般可以使用Redis作为缓存,来缓解数据库如MySQL的压力。使用缓存的步骤为:

(1)从Redis缓存中获取数据,如果存在数据,直接返回值。

(2)如果不存在,执行数据库的查询方法

(3)将数据库中的值放入缓存

NO CODE NO BB,代码如下

//a.从缓存中获取 String value = redisTemplate.opsForValue().get(key); if (value != null) {     log.info("从缓存中读取到值:{}", value);     return value; } //b.从数据库中查询 List<Member> members = memberMapper.listByName(name); //c.同步value到缓存 value = JSONArray.toJSONString(members); redisTemplate.opsForValue().set(key, value, expireTimes, TimeUnit.SECONDS); return value;

如上代码,这里有个问题,我们只是要做个查询而已,也就是只要b行的代码,其他代码不是业务代码,不应该由开发人员去操心。那我们何不用注解的形式代替a和c代码呢。

使用SpringBoot的缓存注解

SpringBoot提供了现成可用的缓存注解@Cacheble。

配置类开启缓存注解

@Configuration @EnableCaching public class RedisConfig extends CachingConfigurerSupport {     @Bean     public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory factory) {         RedisTemplate<Object, Object> template = new RedisTemplate<>();         template.setConnectionFactory(factory);         ...      }      ...               @Bean     public CacheManager cacheManager(RedisConnectionFactory factory) {         RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()                 .entryTtl(Duration.ofSeconds(60))      //缓存过期时间                 .disableCachingNullValues();         return RedisCacheManager.builder(factory)                 .cacheDefaults(config)                 .transactionAware()                 .build();     }  }

使用@Cacheable注解

@Cacheable(value = "member",key = "#name") public List<Member> listByName(String name) {     return memberMapper.listByName(name); }

测试

@Test void listByName() {     String name = "zhouzhou";     List<Member> members = memberController.listByName(name);     log.info("members:{}",members); }

控制台结果

members:[Member(id=1805590839001216, name=zhouzhou, code=109, annotationParam=null)]

上面代码发现使用Spring缓存注解的缓存失效时间还要在配置类中进行配置。于是我在想为什么这个失效时间不做成注解的这一项属性呢,这样自定义失效时间就比较方便了。

自定义缓存注解

注解定义

@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface CustomizeCache {     String key();     String value();     long expireTimes() default 120L; //默认过期时间120s     int semaphoreCount() default Integer.MAX_VALUE;  //默认限制线程并发数 }

参数说明如下:

key():缓存的key,一般是一个动态的参数值

vaule():缓存的value,value::key为Redis缓存中拼接的KEY

expireTimes():缓存失效时间,默认

semaphoreCount():共享锁,并发下允许访问的线程数,用于保护数据库。默认为Integer.MAX_VALUE

AOP注解开发

首先创建切面类

@Component @Aspect @Slf4j public class CacheAspect {    ... }

创建横切面,为注解CustomizeCache添加功能。

@Pointcut("@annotation(com.lvshen.demo.redis.cache.CustomizeCache)")     public void cachePointcut() {     }

开发缓存功能,定义@Around

 @Around("cachePointcut()")     public Object doCache(ProceedingJoinPoint point) {     ...     }

获取方法上注解的内容

Method method = point.getTarget().getClass().getMethod(signature.getName(), signature.getMethod().getParameterTypes()); CustomizeCache annotation = method.getAnnotation(CustomizeCache.class); String keyEl = annotation.key(); String prefix = annotation.value(); long expireTimes = annotation.expireTimes(); int semaphoreCount = annotation.semaphoreCount();

解析SpringEL表达式

Object[] args = point.getArgs(); DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer(); String[] parameterNames = discoverer.getParameterNames(method); for (int i = 0; i < parameterNames.length; i++) {     context.setVariable(parameterNames[i], args[i].toString()); }

拼接Redis KEY

//解析 String key = prefix + "::" + expression.getValue(context).toString();

判断缓存中是否存在

value = redisTemplate.opsForValue().get(key); if (value != null) {     log.info("从缓存中读取到值:{}", value);     return value; }

自定义组件-创建限流令牌

semaphore = new Semaphore(semaphoreCount); boolean tryAcquire = semaphore.tryAcquire(3000L, TimeUnit.MILLISECONDS); if (!tryAcquire) {     //log.info("当前线程【{}】获取令牌失败,等待其他线程释放令牌",   Thread.currentThread().getName());     throw new RuntimeException(String.format("当前线程【%s】获取令牌失败,等带其他线程释放令牌", Thread.currentThread().getName())); }

如果缓存没有数据,则执行原本方法。

 value = point.proceed();

同步value到缓存

redisTemplate.opsForValue().set(key, value, expireTimes, TimeUnit.SECONDS);

最后释放令牌

} finally {     if (semaphore == null) {      return value;     } else {      semaphore.release();     } }

调用注解

@CustomizeCache(value = "member", key = "#name") public List<Member> listByNameSelfCache(String name) {  return memberMapper.listByName(name); }

测试

@Test void testCache() {     String name = "lvshen99";     List<Member> members = memberService.listByNameSelfCache(name);     log.info("members:{}",members); }

测试结果

members:[Member(id=15, name=lvshen99, code=200, annotationParam=null)]

Redis上显示

我们也可以自定义缓存失效时间,如设置失效时间

 @CustomizeCache(value = "member", key = "#name",expireTimes = 3600)  public List<Member> listByNameSelfCache(String name) {   return memberMapper.listByName(name);  }

失效时间为3600ms,如图,显示3585ms是因为截图的时候过了15ms。

源码地址如下:

完整源码Github地址:https://github.com/lvshen9/demo/tree/lvshen-dev/src/main/java/com/lvshen/demo/redis/cache

往期推荐

我写出这样干净的代码,老板直夸我

云南丽江旅游攻略

使用ThreadLocal怕内存泄漏?

Java进阶之路思维导图

程序员必看书籍推荐

3万字的Java后端面试总结(附PDF)

扫码二维码,获取更多精彩。或微信搜Lvshen_9,可后台回复获取资料

1.回复"java" 获取java电子书; 2.回复"python"获取python电子书; 3.回复"算法"获取算法电子书; 4.回复"大数据"获取大数据电子书; 5.回复"spring"获取SpringBoot的学习视频。 6.回复"面试"获取一线大厂面试资料 7.回复"进阶之路"获取Java进阶之路的思维导图 8.回复"手册"获取阿里巴巴Java开发手册(嵩山终极版) 9.回复"总结"获取Java后端面试经验总结PDF版 10.回复"Redis"获取Redis命令手册,和Redis专项面试习题(PDF) 11.回复"并发导图"获取Java并发编程思维导图(xmind终极版)

另:点击【我的福利】有更多惊喜哦。

最新回复(0)