Spring AOP实现对Redis的缓存同步

请求过程:

  1. 根据请求参数生成Key,后面我们会对生成Key的规则,进一步说明;
  2. 根据Key去缓存服务器中取数据,如果取到数据,则返回数据,如果没有取到数据,则执行service中的方法调用dao从DB中获取数据,同时成功后将数据放到缓存中。
  3. 删除、新增、修改会触发更新缓存的拦截类对缓存服务器进行更新。

    1.首先贴上核心注解类

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface RedisLogService {

   
    enum CACHE_OPERATION {
        FIND, // 查询缓存操作
        UPDATE, // 需要执行修改缓存的操作
        INSERT; // 需要执行新增缓存的操作
    }

    /** 存储的分组 */
    String[] group();

    /** 当前缓存操作类型 */
    CACHE_OPERATION cacheOperation() default CACHE_OPERATION.FIND;

    /** 存储的Key 默认加入类名跟方法名 */
    String key() default "";

    /** 是否使用缓存 */
    boolean use() default true;

    /** 超时时间 */
    int expire() default 0;

    enum LOG_OPERATION {
        ON, // 开启日志记录
        OFF, // 关闭日志记录
    }

    /** 当前缓存操作类型 */
    LOG_OPERATION logOperation() default LOG_OPERATION.ON;

    /** 操作名称 */
    String name() default "";

    /** 操作参数 */
    String param() default "";

    /** 日志参数 操作人操作IP,操作IP归属地 */
    String logParam() default "";

 2.使用注解案例。

@RedisLogService(group = {
            "group.news" }, key = "#record", name = "网站维护-公司新闻管理-分页查询公司新闻", param = "#record", logParam = "#map")

    解释下上面注解:根据业务的需要,将缓存key进行分组,第一个group参数即是分组,用来标识某个模块,例如新闻模块统一是group.news;第二个key是根据参数拼接成的key,第三个name只是一个名称而已,没什么太大的作用,主要是用于给其它开发人员理解, 第四个param则是操作参数,这个很重要,到时候会用它来拼接key,第五个logParam是日志。

3.贴上具体拦截类

复制代码

@Aspect
@Order(value = 1)
@Component("redisLogServiceInterceptor")
public class RedisLogServiceInterceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(RedisLogServiceInterceptor.class);

    @Autowired
    private UserLogRecordService userLogRecordService;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 
     * 
     * @Title: execute
     * @Description: 切入点业务逻辑
     * @param proceedingJoinPoint
     * @return
     */
    @Around("@annotation(RedisLogService)")
    public Object execute(ProceedingJoinPoint proceedingJoinPoint) throws ServiceException {
        Object result = null;

        try {
            Method method = getMethod(proceedingJoinPoint);

            // 获取注解对象
            RedisLogService redisLogService = method.getAnnotation(RedisLogService.class);

            // 判断是否使用缓存
            boolean useRedis = redisLogService.use();

            if (useRedis) {

                // 使用redis
                ValueOperations<String, Object> operations = redisTemplate.opsForValue();

                // 判断当前操作
                switch (redisLogService.cacheOperation()) {

                case FIND:

                    result = executeDefault(redisLogService, operations, proceedingJoinPoint, method);

                    break;
                case UPDATE:

                    result = executeUpdate(redisLogService, operations, proceedingJoinPoint);

                    break;
                case INSERT:

                    result = executeInsert(redisLogService, operations, proceedingJoinPoint);

                    break;
                default:

                    result = proceedingJoinPoint.proceed();

                    break;
                }
            } else {

                result = proceedingJoinPoint.proceed();
            }

        } catch (ServiceException e) {
            throw e;
        } catch (Throwable e) {
            throw new ServiceException(new Result<Object>("500", e.getMessage()), e);
        }
        return result;
    }

    /**
     * 
     * @Title: getMethod
     * @Description: 获取被拦截方法对象
     * @param joinPoint
     * @return
     */
    protected Method getMethod(JoinPoint joinPoint) throws Exception {

        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

        Method method = methodSignature.getMethod();

        return method;
    }

    上面的代码使用了@Around环绕切面这个注解,为什么不用@Befor或者@After呢?

     1.因为@Befor是在方法执行开始前才进行切面,而@After是方法结束后进行切面;根据业务场景的需要 @Around 可以在所拦截方法的前后执行一段逻辑,例如在查询前先去redis查数据,发现没有数据再回到service层去执行查db,查完了之后呢并不是马上返回给Controller,还需要把数据重新放到Redis,此时其他线程的请求就可以直接从redis获得数据,减少频繁对数据库的操作。

 4.下面贴上查询的具体实现方法

 View Code

   解释下proceedingJoinPoint.getArgs()作用,了解过aop 以及反射相关的都知道这是从方法内取出传入参数,例如传入的是 (String user,String age), 通过这个方法可以分别得到user和age的值。

例如如下方法:

public Result<PageInfo<WebInfoBase>> findPageByParam(WebInfoFindParam record, Map<String, String> map)

  1. 上面这个方法我是传入的第一个参数是实体bean(传入的查询参数别名一定要写record,因为在aop缓存拦截代码里已经显示规定了)

String[] paraNameArr = u.getParameterNames(method);

   从paraNameArr获取参数的别名分别是record和map, 注意在上面的代码里里我用到了自定义redis注解: key = "#record" , 接着使用SPEL表达式进行解析取出record的值,为什么要取出record的值是因为需要拼接key存放到redis,所以record作为key拼接的条件之一是必须的。

   分析完毕后举个请求的例子

                  假设用户id = 1,分页查询了订单信息,这时候 record 参数为:pageSize:10,pageNum:2,id:1。key的最终格式 :grop+namespace+record(这样基本是唯一不会重复)。

猜你喜欢

转载自blog.csdn.net/qq_33541802/article/details/82627940