2018-09-24

springboot中AOP+redis封装缓存


目录

一、目的
二、配置
--1、pom.xml
--2、redis配置
--3、响应类
--4、AOP切面的配置
三、如何使用自已写的缓存程序


一、目的

在controller层的方法上加一个注释就可以利用aop切面去做代码的扩展,在代码的扩展中利用redis做为缓存中间件。关于注解就用自定义的@RedisCached


二、配置

1、pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、redis配置

基础配置

@Configuration
public class RedisConfig {
    /**
     * @Description: 创建一个模板类,将redis连接工厂设置到模板类中{ @link RedisTemplate}
     * @Author: maozi
     * @Date: 2018/6/5 11:49
     * @see:
     **/
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory factory){
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        return template;
    }
}

再封装一层

@Repository
public class RedisDao {

    /**
     * @Description: 字符类的模板{ @link }
     * @Author: maozi
     * @Date: 2018/6/5 11:52
     * @see:
     **/
    @Autowired
    private StringRedisTemplate stringTemplate;

    /**
     * @Description: 对象类的模板 { @link }
     * @Author: maozi
     * @Date: 2018/6/5 11:53
     * @see:
     **/
    @Autowired
    private RedisTemplate<String, Object> template;

    public void setStringKey(String key, String value,int expire) {
        if(stringTemplate.hasKey(key)){
            stringTemplate.delete(key);
        }

        ValueOperations<String, String> ops = stringTemplate.opsForValue();
        ops.set(key,value,expire, TimeUnit.MINUTES);
    }

    public String getStringValue(String key) {
        ValueOperations<String, String> ops = this.stringTemplate.opsForValue();
        return ops.get(key);
    }

    public Object getValue(String key) {
        return template.opsForValue().get(key);
    }

    public void setKey(String key,Object value,long minutes){
        if(template.hasKey(key)){
            template.delete(key);
        }
        template.opsForValue().set(key,value,minutes, TimeUnit.MINUTES);
    }

    public boolean existByKey(String key){
        return template.hasKey(key);
    }

    public boolean existByStringKey(String key){
        return stringTemplate.hasKey(key);
    }

    public long getExpireTime(String key){
        return template.getExpire(key);
    }

    public Boolean setExpireToReturnYes(String key,long timeout){
        if (!template.hasKey(key)){
            return false;
        }
        return template.expire(key,timeout,TimeUnit.MINUTES);
    }

    public Boolean setStringExpireToReturnYes(String key,long timeout){
        if(!stringTemplate.hasKey(key)){
            return false;
        }
        return stringTemplate.expire(key,timeout,TimeUnit.MINUTES);
    }

    public void cleanCacheByString(String key){
        stringTemplate.delete(key);
    }

    public void cleanCache(String key){
        template.delete(key);
    }
}

注意:这里的模板有分字符的和对象的,如果value是字符的,那建议有字符模板

application.properties

# redis
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.database=1
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
spring.redis.pool.max-idle=500
spring.redis.pool.min-idle=0
spring.redis.timeout=0
3、响应类
@ApiModel(description = "请求返回结果")
public class ResponseResult {
    private String errorCode;
    private String errorMsg;
    private Object objectResult;


    public String getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(String errorCode) {
        this.errorCode = errorCode;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }

    public Object getObjectResult() {
        return objectResult;
    }

    public void setObjectResult(Object objectResult) {
        this.objectResult = objectResult;
    }

    @Override
    public String toString(){
        return JSON.toJSONString(this);
    }

}
4、AOP切面的配置

自定义注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RedisCached {
    public int expire() default 5;//过期时间,默认5分钟
}

切面类

/**
 * @Description: redis缓存的AOP
 * @Author: maozi
 * @Date: 2018/9/3 10:07
 * @see: RedisCached
 **/
@Aspect
@Component
public class RedisAspect {

    private final Logger logger = LoggerFactory.getLogger(RedisAspect.class);

    @Autowired
    RedisDao redisDao;

    @Pointcut("execution(public com.zhengjia.entity.ResponseResult com.zhengjia.web.*.*(..)) && @annotation(com.zhengjia.common.Annotation.RedisCached)")
    public void redisAdvice(){}

    @Around("redisAdvice()")
    public Object Interceptor(ProceedingJoinPoint pjp){
        Object result = null;
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();

        String port = String.valueOf(request.getServerPort());
        String uri = request.getRequestURI();
        String methodType = request.getMethod();
        String queryString = request.getQueryString();

        //反射拿方法信息
        Method method=getMethod(pjp);

        //获得annotation的信息
        RedisCached annotation = method.getAnnotation(RedisCached.class);
        int expire = annotation.expire();

        //检查请求类型
        if(!methodType.equalsIgnoreCase("GET")){
            throw new RuntimeException("只允许get请求做缓存");
        }

        //key的唯一性由url加上参数保证
        String keyName = "";
        if(method.getAnnotation(IgnoreToken.class) == null){ //如果没有注解的话,keyName规则上加上一sourceFrom参数
            String token = request.getHeader("Authorization");
            if(token.isEmpty()) throw new RuntimeException("请求头Authorization中没有token信息");
            Map userMap = (Map)redisDao.getValue(token);
            keyName = (String)userMap.get("sourceFrom") + port + uri + "?" + queryString;
        }else {
            keyName = port + uri + "?" + queryString;
        }

        ResponseResult responseResult = new ResponseResult();

        try {
            if (!redisDao.existByKey(keyName)){//没存在缓存,去查数据库
                result = pjp.proceed();
                responseResult = (ResponseResult)result;
                if(responseResult.getObjectResult() != null){ //防止缓存空值
                    redisDao.setKey(keyName,responseResult.getObjectResult(),expire);
                }
            }else{//存在缓存中,在缓存中取数据库
                responseResult.setObjectResult(redisDao.getValue(keyName));
                responseResult.setErrorCode(Constants.RESPONSE_CODE_SUCCESS);
                responseResult.setErrorMsg(Constants.RESPONSE_MSG_OK);
                result = responseResult;
                logger.info("redis缓存中查询数据,key值为" + keyName);
            }

        } catch (Throwable e) {
            e.printStackTrace();
            responseResult.setErrorCode(Constants.RESPONSE_CODE_ERROR);
            responseResult.setErrorMsg("redisAspect报错!");
            logger.info("redisAspect报错!");
        }
        return result;
    }

    /**
     *  获取被拦截方法对象
     *
     *  MethodSignature.getMethod() 获取的是顶层接口或者父类的方法对象
     *  而缓存的注解在实现类的方法上
     *  所以应该使用反射获取当前对象的方法对象
     */
    public Method getMethod(ProceedingJoinPoint pjp){
        //获取参数的类型
        Object [] args=pjp.getArgs();
        Class [] argTypes=new Class[pjp.getArgs().length];
        if(args.length == 1 && args[0] == null){
            args = new Object[0];
        }
        for(int i=0;i<args.length;i++){
            argTypes[i]=args[i].getClass();
        }
        Method method=null;
        try {
            //有参的情况下
            if(args.length > 0){
                method = pjp.getTarget().getClass().getMethod(pjp.getSignature().getName(), argTypes);
            }else { //无参的情况下
                Class clazz = Class.forName(pjp.getSignature().getDeclaringTypeName());
                Method[] i = clazz.getMethods();
                for(Method data : clazz.getMethods()){
                    if(data.getName().equalsIgnoreCase(pjp.getSignature().getName())){
                        method = data;
                        break;
                    }
                }

            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return method;

    }
}

注意:
1、缓存的key的唯一性是用url来保证,而且这里只允许Get请求,如果想扩展,那就自行修改代码,另外key的唯一性也可以用其他方式来确定。
2、这里缓存ResponseResult 中的objectResult的值,所以要用ResponseResult做返回,另外是因为,返回值在切面中做了限制。也就是说不用ResponseResult做返回值返回,那这个缓存是不起作用的。
3、expire是提供出去,动态设置缓存过期的时间


三、如何使用自已写的缓存程序

/**
  * 商圈-购物中心关联度
  */
@GetMapping(value = "/correlation-degree/{name}")
@RedisCached(expire = 6)
public ResponseResult shopMallCorrelationDegree(@PathVariable String name) {
    ResponseResult responseResult = new ResponseResult();

    List<Map<String, Object>> resultList = bussinessCircleService.getShopMallCorrelationDegree(name);

    responseResult.setErrorCode(Constants.RESPONSE_CODE_SUCCESS);
    responseResult.setErrorMsg(Constants.RESPONSE_MSG_OK);
    responseResult.setObjectResult(resultList);
    return responseResult;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,125评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,293评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,054评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,077评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,096评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,062评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,988评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,817评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,266评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,486评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,646评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,375评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,974评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,621评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,642评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,538评论 2 352