背景
在查询类开发中我们有使用缓存的场景,一般可以使用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 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@EnableCachingpublic class RedisConfig extends CachingConfigurerSupport { @Bean public RedisTemplate redisTemplate(RedisConnectionFactory factory) { RedisTemplate 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 listByName(String name) { return memberMapper.listByName(name);}
测试
@Testvoid listByName() { String name = "zhouzhou"; List members = memberController.listByName(name); log.info("members:{}",members);}
控制台结果
members:[Member(id=1805590839001216, name=zhouzhou, code=109, annotationParam=null)]
![2fb7b761b1bc161a345ade536eca77a0.png](https://img-blog.csdnimg.cn/img_convert/2fb7b761b1bc161a345ade536eca77a0.png)
上面代码发现使用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@Slf4jpublic 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
拼接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 listByNameSelfCache(String name) { return memberMapper.listByName(name);}
测试
@Testvoid testCache() { String name = "lvshen99"; List members = memberService.listByNameSelfCache(name); log.info("members:{}",members);}
测试结果
members:[Member(id=15, name=lvshen99, code=200, annotationParam=null)]
Redis上显示
![904711ade8051d51c1ce365d4fa38da9.png](https://img-blog.csdnimg.cn/img_convert/904711ade8051d51c1ce365d4fa38da9.png)
我们也可以自定义缓存失效时间,如设置失效时间
@CustomizeCache(value = "member", key = "#name",expireTimes = 3600) public List listByNameSelfCache(String name) { return memberMapper.listByName(name); }
![1f76f497881adee28b59f5acede23382.png](https://img-blog.csdnimg.cn/img_convert/1f76f497881adee28b59f5acede23382.png)
失效时间为,如图,显示是因为截图的时候过了。
源码地址如下:
完整源码Github地址:https://github.com/lvshen9/demo/tree/lvshen-dev/src/main/java/com/lvshen/demo/redis/cache