今天来做一个通过注解的形式实现redis缓存
在我们平时的redis的使用中经常会有一种写法,就是先查redis,如果redis中没有,再查数据库。
String key = KEY + "showButton";
if (redisClient.hasKey(key)) {
String str = redisClient.get(key, String.class);
result = JSON.parseArray(str, VaButtonConfig.class);
log.info("从redis获取按钮配置");
return result;
}else {
result = buttonConfigMapper.selectByExample(null);
redisClient.set(key,result,7,TimeUnit.DAYS);
log.info("将按钮配置存入redis");
return result;
}
像这种套路性的代码太多可以说有很多“重复”代码了。这里我们来做一个注解来实现上面的功能,简化我们的代码。
新建一个注解RedisCache,key表示我们redis缓存的key。expire缓存时间
package com.lw.study.redisInterupter;
import java.lang.annotation.*;
/**
* @author
* @date 10:28 2019/6/26
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface RedisCache {
String key();
long expire() default -1;
}
RedisCache上面的三个注解是元注解(用来描述注解的一种方式)
- @Retention //定义注解的生命周期(source-class-runtime)
- @Target //描述注解的应用范围
- @Documented //文档注解 会被javadoc工具文档化
在我们使用redis缓存的时候key值通常会使用字符串(表示业务模块)+特殊符号(冒号,下划线)+特定的参数值来拼接,所以这里我们会用到spring EL表达式。具体使用大家可以查看官方文档,我这里举个例子:用#id来获取参数中id的值,用#name来获取参数中name 的值。然后拼在一起。要注意的是,我们的常量字符串需要用单引号括起来。拼接的字符串之间用加号连接
@Override
@RedisCache(key = "'study:' + #id +':' + #name")
public User selectUserById(int id,String name) {
return userMapper.selectByPrimaryKey(id);
}
重点来了,新建一个切面RedisCacheAspect,通过aop在使用到我们注解的地方把数据存入缓存
@Component
@Aspect
public class RedisCacheAspect {
public static final Logger logger = LoggerFactory.getLogger(RedisCacheAspect.class);
//这里使用StringRedisTemplate来操作redis
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Around("@annotation(com.lw.study.redisInterupter.RedisCache)")
public Object cacheInterceptor(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature signature = (MethodSignature) pjp.getSignature();
//获取到目标方法
Method method = pjp.getTarget().getClass().getMethod(signature.getName(), signature.getParameterTypes());
//获取方法注解
RedisCache redisCache = method.getAnnotation(RedisCache.class);
String keyEl = redisCache.key();
//创建解析器 解析EL表达式
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(keyEl);
//设置解析上下文(这些占位符的值 来自)
EvaluationContext context = new StandardEvaluationContext();
//参数值 获取到参数实际的值
Object[] args = pjp.getArgs();
//我们还需要获取实际的参数名,而不是agrs0,agrs1这种形式,通过下面的方式可以获取
// public DefaultParameterNameDiscoverer() {
// if (standardReflectionAvailable) {
// this.addDiscoverer(new StandardReflectionParameterNameDiscoverer());
// }
//
// this.addDiscoverer(new LocalVariableTableParameterNameDiscoverer());
// }
//他会调用LocalVariableTableParameterNameDiscoverer去实现
DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(method);
for(int i = 0; i < parameterNames.length; i++) {
//name-value : userId-10010
context.setVariable(parameterNames[i],args[i]);
}
//解析出key的真实值
String key = expression.getValue(context).toString();
System.out.println(key);
String object = stringRedisTemplate.opsForValue().get(key);
if (object == null) {
//获取方法执行结果
Object data = pjp.proceed();
System.out.println("从数据库获取结果");
//缓存时间
long expireTime = redisCache.expire();
if (expireTime == -1) {
stringRedisTemplate.opsForValue().set(key,JSON.toJSONString(data));
}else {
stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(data),expireTime, TimeUnit.SECONDS);
}
return data;
}else {
System.out.println("从redis获取");
//这里object套一层JSON.parse()是因为有时候存入redis的json字符串get出来后会多一个“\”转义符号导致直接parse失败
return JSON.parseObject(JSON.parse(object).toString(),signature.getReturnType());
}
}
}
好了,写一个测试类测试一下
@Test
public void testCache() {
User user = userService.selectUserById(1002, "橘子");
System.out.println(user.toString());
}
debug一下,我们的keyEl被解析出来了,然后用这个key去做缓存。先从缓存取数,没有则从数据库取,我们还可以根据业务加上一些特定的逻辑,比如如果redis和数据库都没有数据,为了防止缓存击穿我们可以做参数校验、往redis存入特定的值并设置有效时间(防止无效数据不停的查询数据库)。



需要注意的是这个注解我们设置了有效范围是在方法上,所以当我们的一个方法业务逻辑很多,缓存只是其中的一个环节的时候,我们可以把缓存的环节抽出来做为一个方法。
版权声明:本文为Soda_lw原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。