场景
当我们需要对方法整体进行缓存,但是又不想每次硬编码,希望更加灵活,以组件的形式实现,缓存的操作;
比如:在C端用户请求页面的时候,我们可以直接返回已经缓存好的数据;但是在PC端维护信息时,则需要把C端用户的缓存进行清空。
整体思路
- 新增自定义注解
- ReadThroughSingleCache (通过缓存读取,使用Hash结构)
- InvalidateSingleCache (移除ReadThroughSingleCache 修饰的缓存)
- SimpleReadThroughSingleCache (通过缓存读取,使用Hash结构,并将请求参数作为hash中的key)
- CacheKeyParam (标识缓存自定义key的注解)
- 配置缓存key的枚举类
- CacheEnumKeys
- CacheEnumSimpleKeys
- 通过AOP,实现对注解修饰的方法结果进行缓存
- 使用方式
- 在需要的地方增加注解
核心代码
-
新增自定义注解
- 通过缓存读取,使用Hash结构
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ReadThroughSingleCache { //缓存得key CacheEnumKeys value(); }
- 移除ReadThroughSingleCache 修饰的缓存
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface InvalidateSingleCache { CacheEnumKeys[] value(); }
- 通过缓存读取,使用Hash结构,并将请求参数作为hash中的key
/** * 通过缓存读取 使用 hash结构 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface SimpleReadThroughSingleCache { //缓存得key CacheEnumSimpleKeys value(); String hashKey() default ""; //如果显示设置 缓存key就不会使用其他key的策略 }
- 移除SimpleReadThroughSingleCache 修饰的缓存
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface InvalidateHashKeyCache { CacheEnumSimpleKeys[] value(); }
- 缓存key定义
@Target({ElementType.PARAMETER, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface CacheKeyParam { String value() default ""; }
-
缓存key的枚举类,方便统一管理
- 带参数的配置类
/** * 缓存key 的统一配置 */ public enum CacheEnumSimpleKeys { SCENE_SIMPLE_USER_CARD("cache:XX:XXX:XXXX", "中文描述"); private String scene; private String description; public static Map<String, CacheEnumSimpleKeys> KEYS_SCENE; static { KEYS_SCENE = Arrays.stream(CacheEnumSimpleKeys.values()).collect(Collectors.toMap(CacheEnumSimpleKeys::getScene, Function.identity())); } CacheEnumSimpleKeys(String scene, String description) { this.scene = scene; this.description = description; } public String getScene() { return scene; } public void setScene(String scene) { this.scene = scene; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }
- 不带参数的配置类
/** * 缓存key 的统一配置 */ public enum CacheEnumKeys { SCENE_COMMON_CONSTANT("cache:XX:XXX:XXXX", "常量缓存"); private String scene; private String description; public static Map<String, CacheEnumKeys> KEYS_SCENE; static { KEYS_SCENE = Arrays.stream(CacheEnumKeys.values()).collect(Collectors.toMap(CacheEnumKeys::getScene, Function.identity())); } CacheEnumKeys(String scene, String description) { this.scene = scene; this.description = description; } public String getScene() { return scene; } public void setScene(String scene) { this.scene = scene; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }
- 切面
@Aspect @Component public class CommonsAspect { /** * 缓存操作类 */ @Autowired private ICacheService iCacheService; @Around("@annotation(readThroughSingleCache)") public Object readThroughSingleCache(ProceedingJoinPoint point, ReadThroughSingleCache readThroughSingleCache) throws Throwable { return iCacheService.handleQueryCache(point, readThroughSingleCache); } @Around("@annotation(invalidateSingleCache)") public Object invalidateSingleCache(ProceedingJoinPoint point, InvalidateSingleCache invalidateSingleCache) throws Throwable { //必须限制参数 有没有在 缓存key的 列表里 Object object = point.proceed(); //操作成功在清除缓存,防止操作业务因为事务抛出异常导致 缓存已经清除 CacheEnumKeys[] cacheEnumKeys = invalidateSingleCache.value(); for (CacheEnumKeys cacheEnumKey : cacheEnumKeys) { iCacheService.flushCache(cacheEnumKey.getScene()); } return object; } @Around("@annotation(simpleReadThroughSingleCache)") public Object readThroughSingleCache(ProceedingJoinPoint point, SimpleReadThroughSingleCache simpleReadThroughSingleCache) throws Throwable { //必须限制参数 有没有在 缓存key的 列表里 return iCacheService.handleQuerySimpleCache(point, simpleReadThroughSingleCache); } @Around("@annotation(invalidateHashKeyCache)") public Object invalidateHashKey(ProceedingJoinPoint point, InvalidateHashKeyCache invalidateHashKeyCache) throws Throwable { //必须限制参数 有没有在 缓存key的 列表里 Object object = point.proceed(); //操作成功在清除缓存,防止操作业务因为事务抛出异常导致 缓存已经清除 CacheEnumSimpleKeys[] cacheEnumSimpleKeys = invalidateHashKeyCache.value(); for (CacheEnumSimpleKeys e:cacheEnumSimpleKeys){ iCacheService.flushHashKeyCache(e.getScene(), point); } return object; } }
- 缓存操作类接口
/**
* @version 0.1
* @name ICacheService
* @description 缓存服务接口类
**/
public interface ICacheService {
/**
* 查询方法切面调用缓存处理
* 优先从缓存中查询
* 如果缓存中没有,再从数据库中查询并保存到缓存中
*/
Object handleQueryCache(ProceedingJoinPoint point, ReadThroughSingleCache readThroughSingleCache) throws Throwable;
/**
* 查询方法切面调用缓存处理
* 优先从缓存中查询
* 如果缓存中没有,再从数据库中查询并保存到缓存中
* 自定义key
*/
Object handleQuerySimpleCache(ProceedingJoinPoint point, SimpleReadThroughSingleCache simpleReadThroughSingleCache) throws Throwable;
/**
* 保存
*/
void saveCache(String scene, ProceedingJoinPoint point, Object object);
/**
* 刷新缓存
*
* @param scene
*/
void flushCache(String scene);
/**
* 刷新hashKey缓存
*
* @param scene
* @param point
*/
void flushHashKeyCache(String scene, ProceedingJoinPoint point);
/**
* 查询
*
* @param scene
* @param point
* @return
*/
Object getSimpleCache(String scene, ProceedingJoinPoint point);
/**
* 保存
*
* @param scene
* @param point
* @param object
*/
void saveSimpleCache(String scene, ProceedingJoinPoint point, Object object);
}
- 缓存操作接口实现类
/**
* @version 0.1
* @name CacheServiceImpl
* @description 缓存服务实现类
**/
@Service
public class CacheServiceImpl implements ICacheService {
private static final Logger logger = LoggerFactory.getLogger(CacheServiceImpl.class);
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private HashRedisTemplate hashRedisTemplate;
@Autowired
private RedissonClient redissonClient;
@Autowired
private StringObjectValueRedisTemplate stringObjectValueRedisTemplate;
@Override
public Object handleQueryCache(ProceedingJoinPoint point, ReadThroughSingleCache readThroughSingleCache) throws Throwable {
long timeBegin = System.currentTimeMillis();
Object versioningCache = null;
try {
versioningCache = this.getCache(readThroughSingleCache.value().getScene(), point);
} catch (Exception e) {
logger.error("缓存获取失败:" + e.getMessage(), e);
}
if (versioningCache != null) {
//防止缓存穿透
//((MethodSignature) point.getSignature()).getReturnType()
logger.debug("从缓存中获取数据成功,方法:" + ((MethodSignature) point.getSignature()).getMethod().getName() + ",耗时:" + (System.currentTimeMillis() - timeBegin));
return versioningCache;
}
if (hashRedisTemplate.opsForHash().hasKey(readThroughSingleCache.value().getScene(), completeKey(point))) {
//防止缓存穿透
//((MethodSignature) point.getSignature()).getReturnType()
logger.debug("从缓存中获取数据成功,方法:" + ((MethodSignature) point.getSignature()).getMethod().getName() + ",耗时:" + (System.currentTimeMillis() - timeBegin));
return null;
}
RLock lock = redissonClient.getLock(readThroughSingleCache.value().getScene() + completeKey(point));
Object proceed = getObject(point, lock);
this.saveCache(readThroughSingleCache.value().getScene(), point, proceed);
logger.debug("从数据库中获取数据成功,方法:" + ((MethodSignature) point.getSignature()).getMethod().getName() + ",耗时:" + (System.currentTimeMillis() - timeBegin));
return proceed;
}
@Override
public Object handleQuerySimpleCache(ProceedingJoinPoint point, SimpleReadThroughSingleCache simpleReadThroughSingleCache) throws Throwable {
Object versioningCache = null;
try {
versioningCache = this.getSimpleCache(simpleReadThroughSingleCache.value().getScene(), point);
} catch (Exception e) {
logger.error("自定义hash key方式获取缓存失败:" + e.getMessage(), e);
}
if (versioningCache != null) {
logger.debug("自定义hash key缓存命中");
return versioningCache;
}
if (hashRedisTemplate.opsForHash().hasKey(simpleReadThroughSingleCache.value().getScene(), simpleCompleteKey(point))) {
logger.debug("自定义hash key缓存未被穿透");
return null;
}
logger.debug("缓存击穿");
Object proceed;
RLock lock = redissonClient.getLock(simpleReadThroughSingleCache.value().getScene() + simpleCompleteKey(point));
proceed = getObject(point, lock);
this.saveSimpleCache(simpleReadThroughSingleCache.value().getScene(), point, proceed);
return proceed;
}
public void saveSimpleCache(String scene, ProceedingJoinPoint point, Object object) {
if (object != null) {
logger.debug("写入缓存:" + JSON.toJSONString(object));
hashRedisTemplate.opsForHash().put(scene, simpleCompleteKey(point), object);
}
}
/**
* 获取缓存的Json字符串
*
* @param scene
* @param point
* @return
*/
@Override
public Object getCache(String scene, ProceedingJoinPoint point) {
return hashRedisTemplate.opsForHash().get(scene, completeKey(point));
}
@Override
public Object getSimpleCache(String scene, ProceedingJoinPoint point) {
return hashRedisTemplate.opsForHash().get(scene, simpleCompleteKey(point));
}
/**
* 生成hk
*
* @param point
* @return
*/
private String completeKey(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
String returnType = signature.getReturnType().toGenericString();
String methodName = signature.getMethod().getName();
return MD5Utils.getMD5(methodName + returnType + JSON.toJSONString(point.getArgs()));
}
/**
* 防止缓存击穿
*/
private Object getObject(ProceedingJoinPoint point, RLock lock) throws Throwable {
Object proceed;
boolean tryLock = false;
try {
//加锁防止缓存击穿
tryLock = lock.tryLock(5,10, TimeUnit.SECONDS);
if (tryLock) {
proceed = point.proceed();
} else {
throw new ServiceException("10023", "访问人数过多,请稍后再试");
}
} catch (Exception e) {
logger.error("执行业务逻辑失败", e);
throw e;
} finally {
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
return proceed;
}
@Override
public void saveCache(String scene, ProceedingJoinPoint point, Object object) {
if (object != null) {
logger.debug("写入缓存:" + JSON.toJSONString(object));
hashRedisTemplate.opsForHash().put(scene, completeKey(point), object);
}
}
/**
* 刷新缓存 hash对应的key都清掉
*
* @param scene
*/
@Override
public void flushCache(String scene) {
stringRedisTemplate.delete(scene);
}
@Override
public void flushHashKeyCache(String scene, ProceedingJoinPoint point) {
hashRedisTemplate.opsForHash().delete(scene, simpleCompleteKey(point));
}
/**
* 生产key 根据定制化注解
*/
private String simpleCompleteKey(ProceedingJoinPoint point) {
Object[] args = point.getArgs();
if (args.length == 0) {
throw new ServiceException("10023", "缺少CacheKeyParam注解");
}
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Class<?>[] parameterTypes = method.getParameterTypes();
StringBuilder stringBuilder = new StringBuilder();
int cacheKeyParamNum = 0;
int i = 0;
for (Annotation[] annotations : parameterAnnotations) {
//第一层有多少个参数
for (Annotation annotation : annotations) {
boolean assignableFrom = annotation.annotationType().isAssignableFrom(CacheKeyParam.class);
if (assignableFrom) {
cacheKeyParamNum++;//计数使用
//判断是否是基本数据类型 并拼接 不是直接抛出异常
stringBuilder.append(((CacheKeyParam) annotation).value()).append("&").append(getObjectToString(args[i]));
} else {
boolean cacheBeanParam = annotation.annotationType().isAssignableFrom(CacheBeanParam.class);
if (cacheBeanParam) {
stringBuilder.append(((CacheBeanParam) annotation).value());
Field[] declaredFields = parameterTypes[i].getDeclaredFields();
for (Field field : declaredFields) {
boolean hasKeyParam = field.isAnnotationPresent(CacheKeyParam.class);
if (hasKeyParam) {
CacheKeyParam annotation1 = field.getAnnotation(CacheKeyParam.class);
cacheKeyParamNum++;
try {
//参数的类型
Object paramValue;
Method beanMethod;
if (field.getGenericType().toString().equals("boolean")) {
beanMethod = parameterTypes[i].getMethod("is" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1));
} else {
beanMethod = parameterTypes[i].getMethod("get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1));
}
if (args[i] == null) {
stringBuilder.append(annotation1.value()).append("&null");
} else {
paramValue = beanMethod.invoke(args[i]);
stringBuilder.append(annotation1.value()).append("&").append(getObjectToString(paramValue));
}
} catch (Exception e) {
logger.error("通过反射获取缓存的key组装失败", e);
}
}
}
}
}
}
i++;
}
if (cacheKeyParamNum == 0) {
throw new ServiceException("10023", "缺少CacheKeyParam注解参数");
}
String result = stringBuilder.toString();
if (StringUtils.isBlank(result)) {
throw new ServiceException("10023", "缓存key不能是null");
}
logger.debug("组装的缓存key={}", result);
return result;
}
private String getObjectToString(Object obj) {
if (obj == null) {
return null;
}
if (obj.getClass().isAssignableFrom(String.class)) {
return obj.toString();
}
if (obj.getClass().isAssignableFrom(Date.class)) {
throw new ServiceException("10023", "CacheKeyParam不支持Date类型");
}
try {
boolean type = ((Class<?>) obj.getClass().getField("TYPE").get(null)).isPrimitive();
if (type) {
return String.valueOf(obj);
} else {
throw new ServiceException("10023", "缓存key组装失败CacheKeyParam注解不能修饰非基本数据类型的类型");
}
} catch (Exception e) {
logger.error("缓存key组装失败CacheKeyParam注解不能修饰类的类型", e);
throw new ServiceException("10023", "缓存key组装失败CacheKeyParam注解不能修饰非基本数据类型的类型");
}
}
}
- 调用案例
- 查询不带自定义参数(ReadThroughSingleCache)
@ReadThroughSingleCache(CacheEnumKeys.SCENE_COMMON_CONSTANT) @Override public Entity detail(String code) { return mongoTemplate.findOne(new Query(Criteria.where("code").is(storeCode)), Entity.class); }
- 更新等操作,需要删除ReadThroughSingleCache对应的缓存
@Override @InvalidateSingleCache(value = {CacheEnumKeys.SCENE_COMMON_CONSTANT}) public void delete(String code) { Entity entity = new Entity(); entity.setCode(code); mongoTemplate.remove(entity); }
- 查询带自定义参数(SimpleReadThroughSingleCache)
@SimpleReadThroughSingleCache(CacheEnumSimpleKeys.SCENE_SIMPLE_USER_CARD) @Override public long countUserDesignerCard(@CacheKeyParam("exist") String id, @CacheKeyParam String brand, @CacheKeyParam String designerCode) { //查询数据库的操作 long count = 10; return count; }
- 更新等操作时,清掉缓存
@InvalidateHashKeyCache(CacheEnumSimpleKeys.SCENE_SIMPLE_USER_CARD) @Override public void clearCountUserDesignerCard(@CacheKeyParam("exist") String openId, @CacheKeyParam String brand, @CacheKeyParam String designerCode) { //通过切面 清除缓存 }