GuavaCache使用“通配符”key删除缓存

SpringBoot支持RedisCache和GuavaCache等缓存,RedisCache底层支持通配符删除,GuavaCache本地缓存不支持通配符删除, 解决思路:放弃spring的CacheEvict注解清除缓存,使用自定义注解+AOP实现清除包含指定key的缓存,变相实现通配符删除。

cacheManage 缓存管理器

 private static final int DEFAULT_MAXSIZE = 2000;
  //6分钟
  private static final int DEFAULT_TTL = 360;

  @Bean
  @Override
  public CacheManager cacheManager() {
    GuavaCacheManager cacheManager = new GuavaCacheManager();
    cacheManager.setAllowNullValues(false);
    cacheManager.setCacheBuilder(
            CacheBuilder.newBuilder().
                    expireAfterWrite(DEFAULT_TTL, TimeUnit.SECONDS).
                    maximumSize(DEFAULT_MAXSIZE));

    return cacheManager;
  }

list方法使用Cacheable进行缓存,keyGenerator生成自定义key

  @Override
  @Cacheable(value = "userList",cacheManager="cacheManager", keyGenerator = "userListKeyGenerator", unless = "#result==null")
  public PageInfo<User> list(Integer pageNum, Integer pageSize, UserQuery userQuery) {}

userListKeyGenerator 生成当前用户ID + 方法名 + 参数的key

 @Bean("userListKeyGenerator")
  @Override
  public KeyGenerator keyGenerator() {
    return new KeyGenerator() {
      @Override
      public Object generate(Object o, Method method, Object... objects) {

        UserResp userResp = UserClient.user().currentUser();
        String userId = userResp.getId();
        StringBuffer sb = new StringBuffer();
        sb.append(userId + "-" + method.getName());
        for (Object obj :objects) {
          sb.append("-" + obj);
        }
        return sb.toString();
      }
    };
  }

自定义注解CleanCache

@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Target(value = {ElementType.TYPE,ElementType.METHOD})
public @interface CleanCache {
  String[] cacheNames() default {};
}

update方法使用CleanCache并指定需要清除的cacheNames

  @Override
  @CleanCache(cacheNames = "userList")
  public User update(User user) {}

AOP拦截注解,后置通知异步删除缓存

package com.XXX.XXX.XXX.cache;

import com.XXX.XXX.XXX.XXX.UserClient;
import com.XXX.XXX.XXX.cache.anno.CleanCache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;

/**
 * 解析CleanCache注解使用
 */
@Aspect
@Component
public class CleanCacheAop {

  @Autowired
  private CacheManager cacheManager;

  /**
   * 后置通知
   *
   * @param joinPoint 切点
   */
  @AfterReturning("@annotation(CleanCache)")
  public void doAfrterReturning(JoinPoint joinPoint) {
  // 数据量多时遍历耗时,所以异步删除缓存
    new Thread(new Runnable() {
      @Override
      public void run() {
        String[] needRemovecacheNames = getControllerMethodDescription(joinPoint);
        Assert.notNull(needRemovecacheNames, "needRemovecacheNames is null");
        Collection<String> cacheNames = cacheManager.getCacheNames();

        for (String cn : cacheNames) {
          for (String nrcs : needRemovecacheNames) {
            if (cn.equals(nrcs)) {
              Cache cache = cacheManager.getCache(nrcs);
              com.google.common.cache.Cache<Object, Object> caches = (com.google.common.cache.Cache<Object, Object>)cache.getNativeCache();
              ConcurrentMap<Object, Object> map = caches.asMap();
              Set<Object> objects = map.keySet();
              String userId = ElephantExtensionClient.user().currentUser().getId();
              for (Object key:objects ) {
                if (((String) key).contains(userId)) {
                //删除包含指定userId的key的缓存
                  caches.invalidate(key);
                }
              }
            }
          }
        }
      }
    }).start();
  }

  /**
   * 获取注解中对方法的描述信息
   *
   * @param joinPoint 切点
   * @return cacheNames
   */
  public static String [] getControllerMethodDescription(JoinPoint joinPoint) {
    String targetName = joinPoint.getTarget().getClass().getName();    //获得执行方法的类名
    String methodName = joinPoint.getSignature().getName();            //获得执行方法的方法名
    Object[] arguments = joinPoint.getArgs();                          //获取切点方法的所有参数类型
    try {
      Class targetClass = Class.forName(targetName);
      Method[] methods = targetClass.getMethods();    //获取公共方法,不包括类私有的
      String [] value = null;
      for (Method method : methods) {
        if (method.getName().equals(methodName)) {
          Class[] clazzs = method.getParameterTypes();     //对比方法中参数的个数
          if (clazzs.length == arguments.length) {
            value = method.getAnnotation(EpicCleanCache.class).cacheNames();
            return value;
          }
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
    return null;
  }
}

结束。有错误的或不明白的可留言指出,谢谢。

参与评论 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:数字20 设计师:CSDN官方博客 返回首页

打赏作者

有量天尊ts

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值