用aop加redis实现通用接口缓存

2018-04-03 12:41:34 浏览数 (1)

系统在高并发场景下,最有用的三个方法是缓存,限流,降级。

缓存就是其中之一,目前缓存基本上是用redis或者memcached。

redis和memcached的优势,劣势在哪里,这里就不细说了,各位看官自行研究。

对于一些不经常更新的数据,比如说热门文章等等类的数据, 做缓存能减少数据库的压力。

其实做缓存也简单,在查询地方判断有没有缓存,没有就读数据库,然后缓存结果,有就直接读缓存,返回结果。

这样做没问题,问题是需要开发人员去关心每个方法,都要写一遍判断,不够通用。

于是有人说,能不能像别的框架一样,加个注解,就自动对这个接口做缓存呢?

完全可以啊,今天介绍下用aop 注解 redis来实现统一的缓存功能。

首先我们可以定义一个用于缓存的注解,有缓存的时间,时间的单位属性。

代码语言:javascript复制
/**
 * 缓存数据
 * @author yinjihuan
 *
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Cache {
    /**
     * 过期时间
     * @return
     */
    public abstract long timeOut() default 0;
    /**
     * 过期单位
     * @return
     */
    public abstract TimeUnit timeUnit() default TimeUnit.HOURS;
}

然后呢就是写一个切面,切到所有接口,在执行业务方法前判断是否有缓存,然后进行对应的操作。下面贴出部分代码。

下面操作redis的地方都要捕获异常,防止redis连不上了,连不上就要执行业务的查询方法,不能返回空数据给用户。

代码语言:javascript复制
@Around("execution(* com.cxytiandi.spring_redis.controller.*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    long start = System.currentTimeMillis();
    //是否要缓存
    boolean isCache = false;
    //缓存时间
    Long timeOut = 0L;
    //缓存单位
    TimeUnit timeUnit = null;
    //访问的类对象
    Class<?> cls = joinPoint.getTarget().getClass();
    //正在执行的方法名
    String methodName = joinPoint.getSignature().getName();
    Map<String, Object> map = isCache(cls, methodName);
    isCache = (boolean) map.get("isCache");
    if(isCache){
        timeOut = (long) map.get("timeOut");
        timeUnit = (TimeUnit) map.get("timeUnit");
        //获取执行方法的参数列表
        Object[] args = joinPoint.getArgs(); 
        //得到所有参数的值
        StringBuffer params = new StringBuffer();
        try {
            getCallMethodParams(args, params);
        } catch (Exception e) {
            logger.error("", e);
        }

        String className = cls.getSimpleName();
        //采用访问的类加上访问的方法名加上参数值作为唯一的key
        String key = className   "_"   methodName   "_"   params;
        logger.info("Cache key:" key);

        try {
            if(stringRedisTemplate.hasKey(key)){
                try {
                    return queryCache(start, methodName, key);
                } catch (Exception e) {
                    logger.error("获取redis的缓存数据异常", e);
                }
            } else {
                Object result = joinPoint.proceed();
                try {
                    setCache(timeOut, timeUnit, key, result);
                } catch (Exception e) {
                    logger.error("缓存数据到redis异常", e);
                }
                return result;
            }
        } catch (Exception e) {
            logger.error("", e);
        }
    }
    return joinPoint.proceed();
}

不知道大家有没有看到这行代码,这边要产生一个唯一的key,用来标识某次查询请求

这里用到了类名 方法名然后再加上查询的参数值做为key, 比如说我查询用户张三的数据,缓存起来了,下次再查询张三的,才能把缓存的数据直接返回给用户,不然你查个赵四的也返回张三的那不就有问题了吗。

代码语言:javascript复制
//采用访问的类加上访问的方法名加上参数值作为唯一的key
String key = className   "_"   methodName   "_"   params;

其实代码没多少,主要就是思路要理解清楚就ok。

用的地方就简单了,要缓存就加注解。

代码语言:javascript复制
@RequestMapping("/users")
@Cache(timeOut=30, timeUnit=TimeUnit.SECONDS)
public ResponseData queryUsers() {
    return ResponseData.ok(userService.queryUsers());
}

源码下载:http://cxytiandi.com/code/detail/29

0 人点赞