Spring AOP Redis

大家在项目中应该经常使用到缓存,一般都是先从缓存中拿数据,拿得到就直接返回;拿不到然后才从数据库中获取数据。一般代码都是和业务代码耦合在一起的。那么能不能把缓存的代码抽离开来呢?答案是可以的,看了看Spring Cache的实现,它是基于注解与AOP around通知来实现的。

我们先来看看缓存在业务代码中的一般实现:
1) get

Object cacheValue = cache.get("key");
if(null != cacheValue){
    return cacheValue;
} else {
    Object dbValue = getFromInDb("key");
    cache.set("key", value);
    return dbValue;
}

2) delete

Object cacheValue = cache.get("key");
if(null != cacheValue){
    cache.delete("key");
    db.deleteByKey("key")
} else {
    db.deleteByKey("key")
}

这样缓存模块就与业务模块太耦合了。下面就是我们项目中基于spring cache原理的简易实现,让我们来看一看具体的代码:

1、RedisCacheGet – 缓存获取方法

这个类是用于方法来获取缓存的方法。里面可以调你存放Redis的DBIndex,你存放的key,其中key支持Spring EL表达式。

package com.spring.carl.cache.redis.annotation

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface RedisCacheGet {
    //枚举类型
    public enum DataType {
        CLASS, JSON
    }

    /**
     * 保存数据库
     *
     * @return
     */
    public int DBIndex() default 0;

    /**
     * key值
     *
     * @return
     */
    public String key();

    /**
     * 缓存过期时间默认为不过期,过期时间手动去设定,单位为 S
     * 0:不限制保存时长
     *
     * @return
     */
    public int expire() default 0;

    /**
     * force是否强制刷新数据
     *
     * @return
     */
    public boolean force() default false;

    /**
     * 数据类型
     *
     * @return
     */
    public DataType dataType() default DataType.JSON;

}
2、RedisCacheClean – 缓存删除方法

这个类是用于消除缓存的方法。里面可以调你存放Redis的DBIndex,你存放的key,其中key支持Spring EL表达式,支持批量删除操作。

package com.spring.carl.cache.redis.annotation;

import java.lang.annotation.*;


@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface RedisCacheClean {

    /**
     * 保存数据库
     *
     * @return
     */
    public int DBIndex();

    /**
     * key值
     *
     * @return
     */
    public  String[] key();

    /**
     * 是否批量
     * 
     * @return
     */
    public boolean isBatch() default false;
}
3、RedisCacheAspect

这个类是处理缓存注解的方法,同时支持key为spring EL表达式的实现。

package com.spring.carl.cache.redis.annotation;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.spring.carl.cache.redis.client.JedisClient;
import com.spring.carl.jackson.JacksonUtil;
import lombok.extern.log4j.Log4j2;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;

@Aspect
@Component
@Log4j2
public class RedisCacheAspect {

    private static final int ONEDAY = 60 * 60 * 24; //24h

    @Autowired
    private JedisClient jedisClient;

    @Around("@annotation(RedisCacheGet)")
    public Object cacheGet(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Object[] args = joinPoint.getArgs();
        log.info("redisCache get method.name {}",method.getName());
        return goRedisCacheGet(args, method, joinPoint);

    }

    @Around("@annotation(RedisCacheClean)")
    public Object cacheClean(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Object[] args = joinPoint.getArgs();
        log.info("redisCache clean method.name {}",method.getName());
        return goRedisCacheClean(args, method, joinPoint);

    }

    /**
     * @param args
     * @param method
     * @param joinPoint
     * @return
     * @throws Throwable
     * @description redisCacheGet注解解析方法
     */
    public Object goRedisCacheGet(Object[] args, Method method, ProceedingJoinPoint joinPoint) throws Throwable {

        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = new StandardEvaluationContext();

        //获取被拦截方法参数名列表(使用Spring支持类库)
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paraNameArr = u.getParameterNames(method);
        //把方法参数放入SPEL上下文中
        for (int i = 0; i < paraNameArr.length; i++) {
            context.setVariable(paraNameArr[i], args[i]);
        }
        //如果有这个注解,则获取注解类
        RedisCacheGet methodType = method.getAnnotation(RedisCacheGet.class);
        String key = parser.parseExpression(methodType.key()).getValue(context, String.class);
        log.info("redisCache key {}",key);
        if (methodType.dataType() == RedisCacheGet.DataType.JSON) {//JSON形式保存
            if (methodType.force() == true) {//强制更新数据
                Object object = joinPoint.proceed(args);
                if (object != null) {
                    setRedisValueJson(methodType, key, object);
                }
                //返回值类型
                return object;
            } else {
                if (jedisClient.exists(methodType.DBIndex(), key)) {
                    String json = jedisClient.get(methodType.DBIndex(), key);

                    try {
                        log.debug("redis" + method.getReturnType() + "key{}" + key + "\t value{}" + json);
                        if (method.getGenericReturnType().toString().contains("List") ||
                                method.getGenericReturnType().toString().contains("Set") ||
                                method.getGenericReturnType().toString().contains("Map")) {
                            ObjectMapper mapper = JacksonUtil.getInstance().getObjectMapper();
                            String clazzName = ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0].toString().substring(6);
                            Object o = Class.forName(clazzName).newInstance();
                            JavaType javaType = getCollectionType(mapper, method.getReturnType(), o.getClass());
                            return JacksonUtil.getInstance().json2JavaType(json, javaType);
                        } else {
                            return JacksonUtil.getInstance().json2Bean(json, method.getReturnType());
                        }
                    } catch (Exception e) {
                        log.error(e.getMessage());
                    }
                    return null;
                } else {//查询数据,缓存,返回对象
                    Object object = joinPoint.proceed(args);
                    if (object != null) {
                        setRedisValueJson(methodType, key, object);
                    }
                    return object;
                }
            }
        } else {//CLASS形式保存
            if (methodType.force() == true) {//强制更新数据
                Object object = joinPoint.proceed(args);
                if (object != null) {
                    setRedisValueClass(methodType, key, object);
                }
                //返回值类型
                return object;
            } else {
                if (jedisClient.exists(methodType.DBIndex(), key)) {//对象存在直接返回
                    return jedisClient.getObject(methodType.DBIndex(), key);
                } else {//查询数据,缓存,返回对象
                    Object object = joinPoint.proceed(args);
                    if (object != null) {
                        setRedisValueClass(methodType, key, object);
                    }
                    return object;
                }
            }
        }
    }

    /**
     * @param methodType
     * @param key
     * @param object
     */
    private void setRedisValueClass(RedisCacheGet methodType, String key, Object object) {
        //设置缓存时长
        if (methodType.expire() == 0) {
            jedisClient.set(methodType.DBIndex(), key, object);
        } else if (methodType.expire() == 1) {
            jedisClient.set(methodType.DBIndex(), key, ONEDAY, object);
        } else {
            jedisClient.set(methodType.DBIndex(), key, methodType.expire(), object);
        }
    }

    /**
     * @param methodType
     * @param key
     * @param object
     */
    private void setRedisValueJson(RedisCacheGet methodType, String key, Object object) {

        String jsonStr = JacksonUtil.getInstance().bean2Json(object);
        if (methodType.expire() == 0) {//0:永不过期
            jedisClient.set(methodType.DBIndex(), key, jsonStr);
        } else if (methodType.expire() == 1) {//1:过期时间为24h
            jedisClient.set(methodType.DBIndex(), key, ONEDAY, jsonStr);
        } else {//手动指定
            jedisClient.set(methodType.DBIndex(), key, methodType.expire(), jsonStr);
        }

    }

    /**
     * @param args
     * @param method
     * @param joinPoint
     * @return
     * @throws Throwable
     * @description redisCache 清除方法
     */
    public Object goRedisCacheClean(Object[] args, Method method, ProceedingJoinPoint joinPoint) throws Throwable {
        ExpressionParser parser = new SpelExpressionParser();
        EvaluationContext context = new StandardEvaluationContext();

        //获取被拦截方法参数名列表(使用Spring支持类库)
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paraNameArr = u.getParameterNames(method);
        //把方法参数放入SPEL上下文中
        for (int i = 0; i < paraNameArr.length; i++) {
            context.setVariable(paraNameArr[i], args[i]);
        }
        //如果有这个注解,则获取注解类
        Object object = joinPoint.proceed(args);

        //如果有这个注解,则获取注解类
        RedisCacheClean methodType = method.getAnnotation(RedisCacheClean.class);
        if(methodType.isBatch()){
            for (String str : methodType.key()) {
                String keyStr = parser.parseExpression(str).getValue(context, String.class);
                String[] keys = keyStr.split(",");
                for (String key : keys) {
                    jedisClient.del(methodType.DBIndex(), key);
                }
            }
        } else {
            for (String str : methodType.key()) {
                String key = parser.parseExpression(str).getValue(context, String.class);
                jedisClient.del(methodType.DBIndex(), key);
            }
        }
        return object;
    }


    private static JavaType getCollectionType(ObjectMapper mapper, Class<?> collectionClass, Class<?>... elementClasses) {
        return mapper.getTypeFactory().constructParametricType(collectionClass, elementClasses);
    }
}

4、使用方法

具体注解的使用方法。

package com.spring.carl.onlineorder.order.manager.Impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.spring.carl.common.cache.redis.annotation.RedisCacheClean;
import com.spring.carl.common.cache.redis.annotation.RedisCacheGet;
import com.spring.carl.common.constants.RedisDBIndexConstants;
import com.spring.carl.onlineorder.order.domain.OrderExt;
import com.spring.carl.onlineorder.order.domain.vo.OrderExtQueryBean;
import com.spring.carl.onlineorder.order.manager.OrderExtManager;
import com.spring.carl.onlineorder.order.service.OrderExtService;

/**
 * 订单扩展信息管理接口实现类
 *
 */
@Service
public class OrderExtManagerImpl implements OrderExtManager{

    @Autowired
    private OrderExtService orderExtService;

    @Override
    @RedisCacheClean(key="'store_order_ext_'+#orderExt.merchantId + '_' + #orderExt.orderId", 
            DBIndex = RedisDBIndexConstants.ONLINEORDER)
    public void insertOrderExt(OrderExt orderExt) {
        this.orderExtService.save(orderExt);
    }

    @Override
    @RedisCacheClean(key="'store_order_ext_'+#merchantId + '_' + #orderId", 
        DBIndex = RedisDBIndexConstants.ONLINEORDER)
    public void insertOrderExts(Long merchantId, Long orderId,
            List<OrderExt> orderExts) {
        if(orderExts == null || orderExts.isEmpty()){
            return ;
        }
        this.orderExtService.saveBatch(merchantId, orderExts);
    }

    @Override
    @RedisCacheGet(key="'store_order_ext_'+#merchantId + '_' + #orderId", 
        DBIndex = RedisDBIndexConstants.ONLINEORDER)
    public List<OrderExt> getOrderExts(Long merchantId, Long orderId) {
        OrderExtQueryBean queryBean = new OrderExtQueryBean();
        queryBean.setMerchantId(merchantId);
        queryBean.setOrderId(orderId);

        List<OrderExt> orderExts = 
                this.orderExtService.find(queryBean);
        return orderExts;
    }

}

这样只需要在需要使用注解的上面添加相就的注解可以了。实现了业务代码与缓存代码的解藕。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值