以切面的方式操作缓存,提高代码灵活性

通过自定义注解,实现方法结果缓存读写

场景

当我们需要对方法整体进行缓存,但是又不想每次硬编码,希望更加灵活,以组件的形式实现,缓存的操作;
比如:在C端用户请求页面的时候,我们可以直接返回已经缓存好的数据;但是在PC端维护信息时,则需要把C端用户的缓存进行清空。

整体思路

  • 新增自定义注解
    • ReadThroughSingleCache (通过缓存读取,使用Hash结构)
    • InvalidateSingleCache (移除ReadThroughSingleCache 修饰的缓存)
    • SimpleReadThroughSingleCache (通过缓存读取,使用Hash结构,并将请求参数作为hash中的key)
    • CacheKeyParam (标识缓存自定义key的注解)
  • 配置缓存key的枚举类
    • CacheEnumKeys
    • CacheEnumSimpleKeys
  • 通过AOP,实现对注解修饰的方法结果进行缓存
  • 使用方式
    • 在需要的地方增加注解

核心代码

  1. 新增自定义注解

    • 通过缓存读取,使用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 "";
    }
    
  2. 缓存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;
        }
    }
    
  1. 切面
    @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;
        }
    }
    
  2. 缓存操作类接口
/**
 * @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);

}
  1. 缓存操作接口实现类
/**
 * @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) {
        //通过切面 清除缓存
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值