Redis+MyBatis自定义注解实现缓存

在数据库查询前我想查询是否存在缓存,不存在则查询,这样的重复性操作写在代码里很难看,通过AspectJ的AOP编程,可以很优雅地实现这个缓存过程。
但是在使用过程中,发现Spring自带的@Cacheable注解序列化对象时是使用JDK的序列化工具往Redis里存数据,这样很占Redis的内存,为何不用FastJSON之类的序列化工具序列化对象后往Redis里存JSON字符串呢,更加轻量快捷。
但是JSON在序列化和反序列化的时候需要提供类型,但是类型在SpringCache中并没有提供,那就自己做个吧!

/**
 * 缓存注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisCache {

    /**
     * key的生成策略,支持表达式语言,表达式中多个值用"_"分隔
     * 不填写默认使用当前方法名
     * 例如       #name_#id
     * 最终生成SpEL表达式为     #name+'_'+#id
     */
    String field() default "";

    //JSON序列化的类型
    Class type();

    //默认缓存时间是一天 60*60*24
    long expire() default 86400L;
}
/**
 * 删除缓存注解
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisEvict {
    /**
     * key生成策略,可填写多个,对应于Hash映射中的field
     * 不填写默认删除以当前类全限定名作为key的Hash映射
     */
    String[] field() default {};

    Class type();
}

/**
 * Redis缓存切面处理
 */
@Aspect
@Component
public class RedisCacheAspect {
    @Resource
    private RedisTemplate redisTemplate;

    private final static Logger logger = Logger.getLogger(RedisCacheAspect.class);

    /**
     * 获取或添加缓存
     */
    @Around("@annotation(com.common.cache.redis.RedisCache)")
    public Object RedisCache(final ProceedingJoinPoint jp) throws Throwable {
        Method method = getMethod(jp);
        RedisCache cache = method.getAnnotation(RedisCache.class);
        //根据类名、方法名和参数生成key
        final String key = parseKey(cache.field(), method, jp.getArgs());
        if (logger.isDebugEnabled()) {
            logger.debug("生成key:" + key);
        }
        //得到被代理的方法上的注解
        Class modelType = method.getAnnotation(RedisCache.class).type();
        //检查redis是否有缓存
        String value = (String) redisTemplate.opsForHash().get(modelType.getName(), key);
        Object result;
        if (null == value) {
            //缓存未命中
            if (logger.isDebugEnabled()) {
                logger.debug("缓存未命中");
            }
            //去数据库查询
            result = jp.proceed(jp.getArgs());
            //把序列化结果放入缓存
            redisTemplate.opsForHash().put(modelType.getName(), key, serialize(result));
            //    设置失效时间
            redisTemplate.expire(modelType.getName(), cache.expire(), TimeUnit.SECONDS);
        } else {
            //缓存命中
            if (logger.isDebugEnabled()) {
                logger.debug("缓存命中");
            }
            //得到被代理方法的返回值类型
            Class returnType = ((MethodSignature) jp.getSignature()).getReturnType();
            //反序列化从缓存中拿到的json
            result = deserialize(value, returnType, modelType);
        }
        return result;
    }

    /**
     * 删除缓存
     */
    @Around("@annotation(com.common.cache.redis.RedisEvict)")
    public Object RedisEvict(final ProceedingJoinPoint jp) throws Throwable {
        //得到被代理的方法
        Method method = getMethod(jp);
        //得到被代理方法上的注解
        Class modelType = method.getAnnotation(RedisEvict.class).type();
        if (logger.isDebugEnabled()) {
            logger.debug("清空缓存:" + modelType.getName());
        }
        //判断是否指定了field
        String[] fields = method.getAnnotation(RedisEvict.class).field();
        if (fields.length == 0) {
            //清除类全限定名对应Hash缓存
            redisTemplate.delete(modelType.getName());
        } else {
            //清除指定的field的缓存
            List objects = new ArrayList<>();
            for (String field : fields) {
                if (!StringUtils.isEmpty(field)) {
                    objects.add(field);
                }
            }
            if (objects.size() > 0) {
                redisTemplate.opsForHash().delete(modelType.getName(), (Object[]) fields);
            }
        }
        return jp.proceed(jp.getArgs());
    }
    //FastJSON序列化对象
    private String serialize(Object result) {
        return JSON.toJSONString(result);
    }
    //FastJSON反序列化获得对象
    @SuppressWarnings("unchecked")
    private Object deserialize(String json, Class clazz, Class modelType) {
        //返回结果是List对象
        if (clazz.isAssignableFrom(List.class)) {
            return JSON.parseArray(json, modelType);
        }
        //返回结果是普通对象
        return JSON.parseObject(json, clazz);
    }

    /**
     * 获取被拦截方法对象
     * MethodSignature.getMethod() 获取的是顶层接口或者父类的方法对象
     * 而缓存的注解在实现类的方法上
     * 所以应该使用反射获取当前对象的方法对象
     */
    private Method getMethod(ProceedingJoinPoint pjp) {
        //获取参数的类型
        Class[] argTypes = ((MethodSignature) pjp.getSignature()).getParameterTypes();
        Method method = null;
        try {
            method = pjp.getTarget().getClass().getMethod(pjp.getSignature().getName(), argTypes);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return method;

    }

    private String parseKey(String field, Method method, Object[] args) {
        //SpEL表达式为空默认返回方法名
        if (StringUtils.isEmpty(field)) {
            return method.getName();
        }
        //_号分割
        String SpEL = field.replace("_", "+'_'+");
        //获得被拦截方法参数列表
        LocalVariableTableParameterNameDiscoverer nd = new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = nd.getParameterNames(method);
        //使用SpEL进行key的解析
        ExpressionParser parser = new SpelExpressionParser();
        //SpEL上下文
        StandardEvaluationContext context = new StandardEvaluationContext();
        //把方法参数放入SpEL上下文中
        for (int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i], args[i]);
        }
        return method.getName() + parser.parseExpression(SpEL).getValue(context, String.class);
    }

} 
  

最后别忘了在ApplicationContext.xml中配置我们的切面

    
    <context:component-scan base-package="com.common.cache.redis"/>
    
    <aop:aspectj-autoproxy/>

这样就可以很轻松地使用自定义的缓存注解了

    @RedisCache(field = "#page_#rows_#conditions.pid", type = Dict.class)
    public EasyUIPage selectByPageWithConditions(Integer page, Integer rows, Dict conditions)

你可能感兴趣的:(JavaEE实战)