场景:
项目中通常要使用Redis作为缓存,来提高系统查询的效率,减小数据库的压力,通过封装缓存注解,可以方便的在系统中按需缓存业务数据。
- 缓存自定义注解
可定义如模块、redis key、item值(hash数据类型使用)、type(String、Set)、opType(操作类型:Write、Read)、expire(key过期时间,默认为-1,不过期)。
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface Cache {
int EXPIRE_TIME_DEFAULT_NEVER = -1;
String module();
String key();
String itemKey() default "";
CacheType type();
OpType opType();
String condition() default "";
int expire() default EXPIRE_TIME_DEFAULT_NEVER;
}
- Redis缓存类型
public enum CacheType {
STRING,
SET;
}
- 操作类型
public enum OpType {
WRITE,
READ,
}
- 缓存注解切面
@Slf4j
@Aspect
@Component
public class CacheAspect {
private SpelExpressionParser parserSpel = new SpelExpressionParser();
private DefaultParameterNameDiscoverer parameterNameDiscoverer= new DefaultParameterNameDiscoverer();
@Autowired
private RedisUtil redisUtil;
@Around("@annotation(cache)")
public Object interceptor(ProceedingJoinPoint joinPoint, Cache cache) throws Throwable {
return handleCache(joinPoint, cache);
}
protected Object handleCache(final ProceedingJoinPoint joinPoint, Cache cache) throws Throwable {
Object result = null;
if(cache!=null){
result = joinPoint.proceed();
Object[] arguments = joinPoint.getArgs();
Map<String,Object> obj = BeanUtil.beanToMap(result);
if(OpType.WRITE == cache.opType()){
if(CacheType.SET == cache.type()){
redisUtil.hset(cache.module()+cache.key(), String.valueOf(obj.get(cache.itemKey())),result, cache.expire());
}else if(CacheType.STRING == cache.type()){
redisUtil.set(cache.module()+cache.key(),result,cache.expire());
}
}else if(OpType.READ == cache.opType()){
if(CacheType.SET == cache.type()){
Object o = getValueBySpEL(cache.condition(),joinPoint);
if(o!=null){
result = redisUtil.hget(cache.module()+cache.key(), ((String[]) o));
}
}else if(CacheType.STRING == cache.type()){
result = redisUtil.get(cache.module()+cache.key());
}
}
}
log.info("------从cache执行{}操作,结果:{}",cache.opType(),result);
log.info("------从db执行{}操作,结果:{}",cache.opType(),result);
return result;
}
public Object getValueBySpEL(String key, ProceedingJoinPoint pjp) {
Expression expression = parserSpel .parseExpression(key);
EvaluationContext context = new StandardEvaluationContext();
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Object[] args = pjp.getArgs();
String[] paramNames = parameterNameDiscoverer.getParameterNames(methodSignature.getMethod());
for(int i = 0 ; i < args.length ; i++) {
context.setVariable(paramNames[i], args[i]);
}
return expression.getValue(context);
}
}
- 使用自定义注解
@Cache(module = Constants.MODULE_NAME_RBAC, key = REDIS_KEY, type = CacheType.SET, opType = OpType.WRITE, itemKey = "configKey")
public SysConfigDTO add(SysConfigBo config) {
SysConfig sysConfig = BeanUtil.copyProperties(config, SysConfig.class);
save(sysConfig);
return BeanUtil.copyProperties(sysConfig, SysConfigDTO.class);
}
@Override
@Cache(module = Constants.MODULE_NAME_RBAC, key = REDIS_KEY, type = CacheType.SET, opType = OpType.WRITE, itemKey = "configKey")
public SysConfigDTO update(SysConfigBo config) {
SysConfig sysConfig = BeanUtil.copyProperties(config, SysConfig.class);
updateById(sysConfig);
return BeanUtil.copyProperties(sysConfig, SysConfigDTO.class);
}
@Override
@Cache(module = Constants.MODULE_NAME_RBAC, key = REDIS_KEY, type = CacheType.SET, opType = OpType.READ,condition = "#keys")
public List<SysConfig> queryByKeys(String[] keys) {
return null;
}
-
技术细节
自定义注解可以使用SPEL表达式来动态获取方法传参,如:
通过 condition = “#keys”,可在切面中获取动态参数,此实例是告诉切面按“condition”指定的动态参数值获取缓存中的数据。
@Cache(module = Constants.MODULE_NAME_RBAC, key = REDIS_KEY, type = CacheType.SET, opType = OpType.READ,condition = "#keys")