SSM+自定义注解+AOP完成定制Redis缓存结构

上一篇博客说到虽然使用Spring注解来指定缓存非常方便,但是想要进行非常详细、系统的设计缓存数据结构,仅仅使用自带注解还是完全不够的。
https://blog.csdn.net/weixin_43041241/article/details/89338076
本博客就是之前的博客进行的拓展:使用自定义注解+AOP完成定制Redis缓存结构。
·---------------------------------------------------------------------------------------------
首先,介绍一个数据结构:
NewsDetail是一条新闻,每条新闻都有新闻评论NewsComment。下面正式开始:

一、自定义注解:
1、@RedisSelect用来标记需要缓存查询

package com.cod4man.fakenews.annotation.redis;

import java.lang.annotation.*;

/**
 * Created with IntelliJ IDEA.
 *
 * @author 张鸿杰
 * Date:2019-04-17
 * Time:9:58
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface RedisSelect {
    String group() ;
    String key() ;
    String returnType() default "Object";
    boolean isCollection() default false;
    String comment() default "";
}

2、@RedisDelete 用来标注需要缓存删除

package com.cod4man.fakenews.annotation.redis;

import java.lang.annotation.*;

/**
 * Created with IntelliJ IDEA.
 *
 * @author 张鸿杰
 * Date:2019-04-17
 * Time:10:03
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface RedisDelete {
    String group() ;
    String key() ;
    String returnType() default "Object";
    boolean isCollection() default false;
    String comment() default "";
}

二、使用AOP设计缓存:
jar依赖:

      <dependency>
          <groupId>redis.clients</groupId>
          <artifactId>jedis</artifactId>
          <version>2.7.3</version>
      </dependency>

Redis提供的java操作工具Jedis

    <bean id="jedis" class="redis.clients.jedis.Jedis">
        <constructor-arg name="host" value="127.0.0.1"/>
        <constructor-arg name="port" value="6379"/>
    </bean>
package com.cod4man.fakenews.redisaop;

import com.alibaba.fastjson.JSON;
import com.cod4man.fakenews.annotation.redis.RedisDelete;
import com.cod4man.fakenews.annotation.redis.RedisSelect;
import com.cod4man.fakenews.pojo.NewsComment;
import com.cod4man.fakenews.pojo.NewsDetail;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import redis.clients.jedis.Jedis;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * Created with IntelliJ IDEA.
 *
 * @author 张鸿杰
 * Date:2019-04-16
 * Time:20:39
 */
@Aspect
public class FindAllNewsDetailAop {
    private static final Logger log = Logger.getLogger(FindAllNewsDetailAop.class);
    //Redis提供的java操作工具
    @Autowired
    private Jedis jedis ;
    
    //AOP拦截RedisSelect注解,查询缓存操作
    @Around("@annotation(com.cod4man.fakenews.annotation.redis.RedisSelect)")
    public Object aroundLogger_SetAll(ProceedingJoinPoint jp) throws Throwable {
        log.info("调用" + jp.getTarget() + "的" + jp.getSignature().getName() + "方法!方法参数为" + Arrays.toString(jp.getArgs()));
        try {
            //拿到方法
            Method method= getMethod(jp);
            //拿到注解的group属性值
            String redisKey = method.getAnnotation(RedisSelect.class).group();
            //判断是新闻还是评论,这里是将新闻列表和评论列表集合到同一个AOP方法进行处理
            if ("CommentsByNewsId".equals(redisKey)) {
                //Redis的键拼上评论id
                redisKey += jp.getArgs()[0].toString();
            }
            //AOP拦截到查询后先不查主数据库,而是先从Redis缓存中查找
            Map<String, String> mapNews =  jedis.hgetAll(redisKey);
            if (mapNews != null && !"{}".equals(mapNews.toString())) {
                List<Object> list = new ArrayList<>();
                for (String value : mapNews.values()) {
                    list.add(JSON.parse(value));
                }
                //缓存中有数据,则进行数据处理后,直接return结束方法,那么就不会再查询主数据库,从而减轻压力
                return list;
            }
            //如果查询不到,说明是第一次查询,就需要查询数据库
            //使用反射拿到返回值类型
            Type returnType = method.getGenericReturnType();
            Object result = jp.proceed();
            System.out.println("returnType" + returnType.toString());
            //将查询到的数据按一定数据结构存入Redis缓存
            if ("java.util.List<com.cod4man.fakenews.pojo.NewsDetail>".equals(returnType.toString())) {
                List<NewsDetail> list = (List<NewsDetail>) result;
                for (NewsDetail newsDetail : list) {
                    //存入数据格式:大Key==》NewsDetail;
                    // 小Key ==》新闻id(可以保证唯一)NewsDetail.getId();
                    // Value==》新闻详情NewsDetail
                    jedis.hset(redisKey, newsDetail.getId().toString(), JSON.toJSONString(newsDetail));
                }
            } else if ("java.util.List<com.cod4man.fakenews.pojo.NewsComment>".equals(returnType.toString())) {
                List<NewsComment> list = (List<NewsComment>) result;
                for (NewsComment newsComment : list) {
                    //存入数据格式:大Key==》NewsComment+(新闻编号NewsDetail.getId());
                    // 小Key ==》新闻评论id(可以保证唯一)NewsComment.getId();
                    // Value==》评论详情NewsComment
                    jedis.hset(redisKey, newsComment.getId().toString(), JSON.toJSONString(newsComment));
                }
            }
            log.info("调用" + jp.getTarget() + "的" + jp.getSignature().getName()
                    + "方法!方法参数为" + Arrays.toString(jp.getArgs()) + "。方法的返回值为:" + result);
            return result;
        }  catch (Throwable throwable) {
            log.error(jp.getSignature().getName() + "方法发生了异常," + throwable);
            throwable.printStackTrace();
            throw throwable;
        } finally {
            log.info(jp.getSignature().getName() + "方法执行结束!");
        }
    }
    
    //拦截@RedisDelete,删除缓存操作
    @Around("@annotation(com.cod4man.fakenews.annotation.redis.RedisDelete)")
    public Object around_DeleteById(ProceedingJoinPoint jp) throws Throwable {
        log.info("调用" + jp.getTarget() + "的" + jp.getSignature().getName() + "方法!方法参数为" + Arrays.toString(jp.getArgs()));
        try {
            Method method = getMethod(jp);
            // 获取注解对象
            RedisDelete redisDelete = method.getAnnotation(RedisDelete.class);
            //获取注解的group属性
            String redisKey = redisDelete.group();
            boolean mark = true;
            //该删除也是集成了评论删除和新闻删除,如果是评论删除,则拼接上新闻的id来组成大Key
            if ("CommentsByNewsId".equals(redisKey)) {
                redisKey += jp.getArgs()[0].toString();
            }
            //查询Redis中是否有缓存
            Map<String, String> mapNews =  jedis.hgetAll(redisKey);
            //直接查缓存
            if ( mapNews == null || "{}".equals(mapNews.toString())) {
                if (!"CommentsByNewsId".equals(redisKey)) {
                    //判断缓存中是否有该键,没有说明数据也没有,就无需访问数据库了
                    mark = mapNews.containsKey(jp.getArgs()[0].toString());
                }
                if (mark) {
                    System.out.println("缓存中没有key");
                    //缓存中没有该Key,直接return,
                    return -1;
                }
            }
            //否则,缓存中有该Key即数据库有数据,需要删除
            Object result = jp.proceed();
            if (!redisKey.contains("CommentsByNewsId")) {
                //删除缓存
                jedis.hdel(redisKey, jp.getArgs()[0].toString());
            } else {
                System.out.println("评论Id" + redisKey);
                System.out.println("是否删除评论:" + jedis.del(redisKey));
            }
            log.info("调用" + jp.getTarget() + "的" + jp.getSignature().getName()
                    + "方法!方法参数为" + Arrays.toString(jp.getArgs()) + "。方法的返回值为:" + result);
            //删除数据库
            return result;
        }  catch (Throwable throwable) {
            log.error(jp.getSignature().getName() + "方法发生了异常," + throwable);
            throwable.printStackTrace();
            throw throwable;
        } finally {
            log.info(jp.getSignature().getName() + "方法执行结束!");
        }
    }
    //同理可以写增加、修改数据的AOP
    /**
     * 获取被注释方法
     * @Title: getMethod
     * @Description: 获取被拦截方法对象
     * @param joinPoint
     * @return
     */
    protected Method getMethod(JoinPoint joinPoint) throws Exception {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        Method realMethod = joinPoint.getTarget().getClass().getDeclaredMethod(methodSignature.getName(), method.getParameterTypes());
        return realMethod;
    }
}

三、测试:
1、新闻详情业务处理NewsDetailServiceImpl.java

	@Override	
    @RedisSelect(group = "NewsDetail", key = "", comment = "查询全部")
    public List<NewsDetail> findAllNews(int startPage, int pageSize) {
        System.out.println("查询全部");
        return newsDetailMapper.findAllNews((startPage-1)*pageSize,pageSize);
    }
    @Override
    @RedisDelete(group = "NewsDetail", key = "")
    public int deleteNewsById(long newsId) {
        if (newsDetailMapper.deleteNewsById(newsId) >0) {
            return newsCommentService.deleteCommentByNewsId(newsId);
        }
        return -1;
    }

2、新闻评论业务处理NewsCommentServiceImpl.java

    @Override
    @RedisDelete(group = "CommentsByNewsId", key = "")
    public int deleteCommentByNewsId(long newsId) {
        return newsCommentMapper.deleteCommentByNewsId(newsId);
    }

    @Override
    @RedisSelect(group = "CommentsByNewsId", key = "")
    public List<NewsComment> findCommentsByNewsId(long newsId) {
        System.out.println("查看什么");
        return newsCommentMapper.findCommentsByNewsId(newsId);
    }

3、运行结果:新闻以及评论按AOP中所设计的数据结构进行存储,这样在更新操作的时候,就可以指定更新数据内部的一行小数据,虽然更新操作也是需要更新主数据库,但是查询操作就不必再查询数据库了,只需要查询Redis缓存。
在这里插入图片描述有什么不妥的欢迎大家指正!需要源码的可以留言联系我。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值