我们使用redis缓存大多数用的是差不多的模板,代码侵入性大,此处加个注解,方便使用。
注解:
package com.sd.outbound.common.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 注解 ListCacheData 用于简便处理需要进行缓存的操作 * 注意 增加了全局缓存开关,参数为 global_list_cache_open_status_key, * 若是要关闭所有使用这个注解的缓存,可在nacos 配置中心或者其他配置文件配置 global_list_cache_open_status_key: false 即可关闭 * 若是要关闭单个使用注解缓存的地方,在nacos 配置中心或者其他配置文件的地方 配置 prefix: false 即可 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface ListCacheData { /** * 前缀 * @return */ String prefix(); /** * 作为key的参数是在方法的第几个参数 * @return */ int suffixParamIndex() default 0; /** * 后缀字段,,为空时候表示不需要后缀表达式,采用 SPEL * eg: * 1.不支持加固定后缀!!! 目前spEl数据源是来自方法入参,要是需要固定后缀,放置在前缀就好了; * 2.访问参数 比如 methodName(List<String userId, String pageId) ->"#userId+'_'+#pageId"; * 3.访问对象内属性 比如 methodName(UserBO userBO) ->"#userBO.name"; * 4.访问集合数据 比如 methodName(List<String> list) -> "#list.toString()"; * * @return */ String suffixExpression() default ""; /** * 后缀字段,,为空时候表示不需要后缀表达式,采用 SPEL * eg: * 1.不支持加固定后缀!!! 目前spEl数据源是来自方法入参,要是需要固定后缀,放置在前缀就好了; * 2.访问参数 比如 methodName(List<String userId, String pageId) ->"#userId+'_'+#pageId"; * 3.访问对象内属性 比如 methodName(UserBO userBO) ->"#userBO.name"; * 4.访问集合数据 比如 methodName(List<String> list) -> "#list.toString()"; * * @return */ String suffixField(); /** * 缓存时间 秒 * @return */ long expireSecond() default 300; }
切面:
package com.sd.outbound.core.aspect; import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson.JSON; import com.sd.outbound.common.annotation.ListCacheData; import com.sd.outbound.core.CoreConstants; import com.sd.outbound.core.domain.bo.UserBO; import com.soudian.common.StringHelper; import com.soudian.utils.CollectionUtils; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.LocalVariableTableParameterNameDiscoverer; import org.springframework.core.env.Environment; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.*; import java.util.concurrent.TimeUnit; @Slf4j @Aspect @Component public class ListCacheDataAspect { private ExpressionParser parser = new SpelExpressionParser(); private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer(); @Autowired StringRedisTemplate stringRedisTemplate; @Autowired private Environment env; @Around("@annotation(listCacheData)") public Object listCacheDataAround(ProceedingJoinPoint pjp, ListCacheData listCacheData) throws Throwable { String prefix = listCacheData.prefix(); String currentCacheOpenStatus = env.getProperty(prefix); String globalCacheStatus = env.getProperty(CoreConstants.GLOBAL_LIST_CACHE_OPEN_STATUS_KEY); log.info("listCacheOpenStatus of {} = {}, globalListCacheStatus={}", prefix, currentCacheOpenStatus, globalCacheStatus); if (Boolean.FALSE.toString().toLowerCase().equals(globalCacheStatus) || Boolean.FALSE.toString().toLowerCase().equals(currentCacheOpenStatus)) { return pjp.proceed(); } long expireSecond = listCacheData.expireSecond(); int suffixParamIndex = listCacheData.suffixParamIndex(); Method method = getMethod(pjp); Type genericReturnType = method.getGenericReturnType(); Class<?> returnType = method.getReturnType(); Object arg = null; if (suffixParamIndex >= pjp.getArgs().length || (arg = pjp.getArgs()[suffixParamIndex]) == null) { return pjp.proceed(); } if (arg instanceof List) { List list = (List) arg; if (CollectionUtils.isEmpty(list)) { return pjp.proceed(); } List cachedList = new ArrayList(); Map mapResult = new HashMap(); List listResult = new ArrayList(); Boolean isMap = false; if(returnType.getSimpleName().equals("Map")){ isMap = true; } for (Object o : list) { String key = StrUtil.join(StrUtil.COLON, prefix, parseSpel(o, listCacheData.suffixExpression())); String data = stringRedisTemplate.opsForValue().get(key); if (!StringHelper.isEmpty(data)) { cachedList.add(o); Object itemResult = JSON.parseObject(data, genericReturnType); if(isMap){ Map itemMap = (Map) itemResult; mapResult.put(o, itemMap.get(o)); }else{ listResult.add(itemResult); } } } if (cachedList.size() < ((List<?>) arg).size()) { for (Object o : cachedList) { ((List<?>) arg).remove(o); } Object result = pjp.proceed(); if(result instanceof Map){ Map dbMapResult = (Map) result; if(CollectionUtils.isEmpty(dbMapResult)){ return mapResult; } for (Object o : dbMapResult.keySet()) { Map map = new HashMap(1, 1); map.put(o, dbMapResult.get(o)); mapResult.put(o, dbMapResult.get(o)); stringRedisTemplate.opsForValue().set(StrUtil.join(StrUtil.COLON, prefix, parseSpel(o, listCacheData.suffixExpression())), JSON.toJSONString(map), expireSecond, TimeUnit.SECONDS); } }else if(result instanceof List){ List dbListResult = (List) result; if(CollectionUtils.isEmpty(dbListResult)){ return listResult; } for (Object o : dbListResult) { stringRedisTemplate.opsForValue().set(StrUtil.join(StrUtil.COLON, prefix, parseSpel(o, listCacheData.suffixField())), JSON.toJSONString(Arrays.asList(o)), expireSecond, TimeUnit.SECONDS); listResult.add(o); } } log.info("CacheData get data from db in method:{}", method.getName()); log.info("listCacheDataAround pjp absentList = {}", arg); }else { return isMap ? mapResult: listResult; } } return pjp.proceed(); } private Object parseSpel(Object item, String spel) { if(StringHelper.isEmpty(spel)){ return item; } spel = "#item."+spel; EvaluationContext context = new StandardEvaluationContext(); context.setVariable("item", item); try { Expression expression = parser.parseExpression(spel); return expression.getValue(context); } catch (Exception e) { return StrUtil.EMPTY; } } private static Method getMethod(ProceedingJoinPoint pjp) { MethodSignature signature = (MethodSignature) pjp.getSignature(); Method method = signature.getMethod(); if (method.getDeclaringClass().isInterface()) { try { method = pjp .getTarget() .getClass() .getDeclaredMethod(pjp.getSignature().getName(), method.getParameterTypes()); } catch (SecurityException | NoSuchMethodException e) { throw new RuntimeException(e); } } return method; } }