当使用缓存的时候,需要先查询缓存是否有数据,如果有就直接返回;如果没有,就需要去查询数据库,再写进缓存中(可根据结果判断是否需要写入)。每次查询都需要做这个步骤,难免会有点繁琐,可以使用自定义注解,加上sprignAOP实现写入缓存功能。
一、前置准备
- 需要学会使用自定义注解,AOP,可以借鉴这篇博客https://blog.youkuaiyun.com/Proxbj/article/details/123910599
二、代码实现
1.自定义注解
package com.frame.util.annotion;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* @Author: towards
* @CreateTime: 2022-07-17 14:36
* @Description: TODO
* @Version: 1.0
*/
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisCache {
// 缓存键
String key() default "";
// 缓存键生成策略
String keyGenerator() default "";
// 缓存时长
long timeout() default 1;
// 缓存时间单位
TimeUnit timeUnit() default TimeUnit.HOURS;
// 缓存条件判断
String condition() default "";
boolean sync() default false;
String md5() default "";
String id() default "";
}
2.切面
package com.frame.util.aspect;
import com.frame.util.annotion.RedisCache;
import com.frame.util.entity.vo.ResultVo;
import com.frame.util.service.redis.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
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.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @Author: towards
* @CreateTime: 2022-07-17 17:12
* @Description: TODO
* @Version: 1.0
*/
@Component
@Slf4j
@Aspect
public class CacheAspect {
@Autowired
RedisService redisService;
@Pointcut("@annotation(com.frame.util.annotion.RedisCache)")
public void pc(){};
// @Before("pc()")
// public <T> ResultVo before(JoinPoint joinPoint){
// Method method = getMethod(joinPoint);
// RedisCache annotation = method.getAnnotation(RedisCache.class);
// String redisKey = generatorRedisKey(annotation,joinPoint);
// T value = redisService.getKey(redisKey);
// return ResultVo.success(value);// 前置通知。return依然会执行切点的方法逻辑
// }
//
// @AfterReturning(value = "@annotation(com.frame.util.annotion.RedisCache)",returning = "result")
// public void afterReturn(JoinPoint joinPoint,ResultVo result){
// Method method = getMethod(joinPoint);
// RedisCache annotation = method.getAnnotation(RedisCache.class);
// long timeout = annotation.timeout();
// TimeUnit timeUnit = annotation.timeUnit();
// if (result.getCode() == "00000" && ObjectUtils.isNotEmpty(result.getData())){
// // 存入redis
// String redisKey = generatorRedisKey(annotation,joinPoint);
// redisService.setKey(redisKey,result,timeout,timeUnit);
// }
// }
// @Around(value = "@annotation(com.frame.util.annotion.RedisCache)")
@Around("pc()")
public <T> ResultVo around(ProceedingJoinPoint joinPoint) throws Throwable {
Method method = getMethod(joinPoint);
RedisCache annotation = method.getAnnotation(RedisCache.class);
// 查询redis是否有缓存
String redisKey = generatorRedisKey(annotation,joinPoint);
T value = redisService.getKey(redisKey);
// 缓存中存在,直接返回
if (ObjectUtils.isNotEmpty(value)){
return ResultVo.success(value);
}
// 缓存中不存在,执行方法
ResultVo resultVo = (ResultVo) joinPoint.proceed();
// 将结果存入redis
long timeout = annotation.timeout();
TimeUnit timeUnit = annotation.timeUnit();
if (resultVo.getCode().equals("00000") && ObjectUtils.isNotEmpty(resultVo.getData())){
// 存入redis
redisService.setKey(redisKey,resultVo,timeout,timeUnit);
}
return resultVo;
}
public Method getMethod(JoinPoint joinPoint){
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
// 获取方法
Method method = methodSignature.getMethod();
return method;
}
/**
* @description: 生成redis键
* @author: towards
* @date: 18:55
* @param: id
* @param: md5
* @return: java.lang.String
**/
public String redisKey(String id,Map md5){
return "PHONE_"+id+"_"+md5.hashCode();
}
/**
* @description: 根据注解获取参数并生成redis键
* @author: towards
* @date: 2022/7/17 20:02
* @param: annotation
* @param: joinPoint
* @return: java.lang.String
**/
public String generatorRedisKey(RedisCache annotation,JoinPoint joinPoint){
// 获取被注解方法的形参
String md5 = annotation.md5();
Map md5Key = (Map) generateKeyBySpEL(md5, joinPoint);
String id = annotation.id();
String idKey = String.valueOf(generateKeyBySpEL(id, joinPoint));
String redisKey = redisKey(idKey, md5Key);
return redisKey;
}
/**
* @description: 动态获取被注解的方法上的参数,使用 #param 格式获取
* @author: towards
* @date: 20:11
* @param: spELString
* @param: joinPoint
* @return: java.lang.String
**/
public Object generateKeyBySpEL(String spELString, JoinPoint joinPoint){
if(StringUtils.isBlank(spELString)){
return null;
}
// 通过joinPoint获取被注解方法
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
//创建解析器
SpelExpressionParser parser = new SpelExpressionParser();
//获取表达式
Expression expression = parser.parseExpression(spELString);
//设置解析上下文(有哪些占位符,以及每种占位符的值)
EvaluationContext context = new StandardEvaluationContext();
//获取参数值
Object[] args = joinPoint.getArgs();
//获取运行时参数的名称
DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(method);
for (int i = 0; i < parameterNames.length; i++) {
context.setVariable(parameterNames[i],args[i]);
}
//解析,获取替换后的结果
Object result = expression.getValue(context);
return result;
}
}
- 注意这里需要使用环绕通知,前置通知执行完之后,会继续执行方法逻辑,可以在环绕通知执行方法逻辑前return。
- 动态获取参数值可以使用上面的generateKeyBySpEL方法
3.使用该注解
@RedisCache(timeout = 100,timeUnit = TimeUnit.SECONDS,md5 = "#mapParam",id = "#id")
// @CacheInterCeptor(md5 = "#mapParam",id = "#id")
public <T> ResultVo getPhone(Integer id, Map<String, Object> mapParam) throws Exception {
T value = redisService.getKey(keyGenorate(id));
TbPhone tbPhone = tbPhoneDao.queryById(id);
if (!ObjectUtils.isEmpty(tbPhone)){
log.info("query from mysql ,and save to redis");
return ResultVo.success(tbPhone);
}
// 缓存和数据库都没有
return ResultVo.failure(ResultCode.CRUD_ERROR);
}