1.创建自定义缓存注解
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
* @author qinkun
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomCacheable {
/**
* key的前缀 如: sys:dept:
* @return
*/
String cacheNames();
/**
* key值 如: 1 配合cacheNames 拼接成完整值 sys:dept:1
* @return
*/
String key();
/**
* 过期时间 默认600
* @return
*/
int expire() default 600;
/**
* 时间单位 默认秒 (默认时间+单位 过期时间为:600s)
* @return
*/
TimeUnit unit() default TimeUnit.SECONDS;
}
2.创建缓存切面
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.lang.reflect.Method;
/**
* @author qinkun
*/
@Aspect
@Component
public class CustomCacheAspect {
@Resource
private RedisTemplate redisTemplate;
/**
* 定义切点
*/
@Pointcut("@annotation(xxx.CustomCacheable)")//需指定对应的包名
public void preventDuplication() {
}
@Around("preventDuplication()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
//获取执行方法
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
CustomCacheable annotation = method.getAnnotation(CustomCacheable.class);
//拼接完整的key
String cacheKey = annotation.cacheNames() + getKey(annotation.key(), joinPoint);
Object value = redisTemplate.opsForValue().get(cacheKey);
if (value == null) {
//缓存中无值,将方法获取的返回值放入缓存,并返回
Object result = joinPoint.proceed();
//设置缓存,使用注解中的时间和单位
redisTemplate.opsForValue().set(cacheKey, result, annotation.expire(), annotation.unit());
return result;
} else {
//缓存中有值,直接返回缓存中的值
return value;
}
}
private String getKey(String key, ProceedingJoinPoint joinPoint) {
//判断key如果是以'#'开始,则根据SpEL表达式获取key
if (key.contains("#")) {
key = SpelUtil.generateKeyBySpEL(key, joinPoint);
}
return key;
}
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
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 java.lang.reflect.Method;
import java.util.Objects;
/**
* SpEL表达式工具类
*
* @author : raoheng
* @version : v1.0
* @createTime : 2022/4/14 10:23
*/
public class SpelUtil {
/**
* 用于SpEL表达式解析
*/
private static final SpelExpressionParser parser = new SpelExpressionParser();
/**
* 用于获取方法参数定义名字.
*/
private static final DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
/**
* 根据SpEL表达式获取key
*
* @param spELString spEL表达式
* @param joinPoint joinPoint
* @return
*/
public static String generateKeyBySpEL(String spELString, ProceedingJoinPoint joinPoint) {
// 通过joinPoint获取被注解方法
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
// 使用spring的DefaultParameterNameDiscoverer获取方法形参名数组
String[] paramNames = nameDiscoverer.getParameterNames(method);
// 解析过后的Spring表达式对象
Expression expression = parser.parseExpression(spELString);
// spring的表达式上下文对象
EvaluationContext context = new StandardEvaluationContext();
// 通过joinPoint获取被注解方法的形参
Object[] args = joinPoint.getArgs();
// 给上下文赋值
for (int i = 0; i < args.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
// 表达式从上下文中计算出实际参数值
/*如:
@annotation(key="#student.name")
method(Student student)
那么就可以解析出方法形参的某属性值,return “xiaoming”;
*/
return Objects.requireNonNull(expression.getValue(context)).toString();
}
}
3.使用自定义缓存注解
@Override
//过期时间 1 天
@CustomCacheable(cacheNames = "sys:dept:", key = "#param.id",expire = 1,unit = TimeUnit.DAYS)
public User getUser(QueryParam param) {
return sysUserService.getId(param.getId());
}