Spring AOP 实战经验

最近把项目里用到 redis 的部分用 aop 进行重构,记录下来以后回顾

重构前

先看下重构前的代码

/**
 * 获取栏目下的专辑
 * 
 * @return
 */
@RequestMapping("category_albums.do")
@ResponseBody
public Result listCategoryAlbums(@RequestParam int categoryId, @RequestParam int page) {
    //防止客户端忘记传page的值,默认1
    if (page <= 0) {
        page = Constant.DEFAULT_PAGE;
    }

    //准备数据
    String redisKey = RedisKeys.TV_VIDEO_CATEGORY_ALBUM_DETAIL;
    int expireTime = CommonConstants.API_CACHE_EXPIRE_INTERVAL;
    // 从redis中获得对象实体
    AlbumsVo albumData = redisManager.getDataFromRedis(AlbumsVo.class, redisKey, categoryId,
            page);
    if (null == albumData) {
        albumData = albumService.getCatagoryAlbumsData(categoryId, page,
                CommonConstants.PAGENUM);
        redisManager.setDataToRedisAsync(albumData, expireTime, redisKey, categoryId, page);
    } else {
        albumBlockedCache.clearBlocked(albumData.getAlbumList());
    }

    return new Result(albumData);
}

解析

这段代码很简单,向客户端返回某个栏目下的专辑,处理逻辑

  • 首先到 redis 里获取结果,如果命中的话,直接返回给客户端

  • 如果未命中,则到数据库里获取结果,将数据库里获取到的结果缓存到 redis,并返回给客户端

很多接口都是类似的处理逻辑,再看一个例子

重构思路

通过 spring 的 aop 技术,拦截从数据库获取结果的方法,在从数据库获取结果之前,先从 redis 获取,如果 redis 命中,则直接返回;否则就继续执行从数据库获取的方法,将返回值缓存到 reids 并返回

实际上不限于从数据库获取结果,例如上例的hotwordSearch(),实际上并非从数据库获取结果,是调用的 cp 的接口获取的结果

重构步骤

标记要拦截的方法

自定义注解

显然用注解是个很不错的想法,定义如下的注解

这个注解用在需要拦截的方法上,还附带了一些元信息

在目标方法上使用注解

编写拦截器

配置 spring 使拦截生效

重构原代码

现在,拦截器承包了对 redis 的操作......,业务代码里就不需要那些相关的代码了

......

和重构前的代码相比,简洁多了,整个世界都干净了......

redis 穿透

通常情况下,都是优先从 redis 里查询结果。但也有时候需要穿透 redis,到 db 里去获取结果的。

例如,在运营后台修改了栏目的配置条件,这样栏目下的内容就会发生变化,此时数据库里的数据和缓存就不同步了,这时的逻辑应该是从数据库查询数据,并刷新缓存里的数据

@Redis注解也支持这种操作,只需要设置 action 属性为 STAB_REDIS 即可

但是,同一个方法,action 要么是 STAB_REDIS,要么是 REDIS_FIRST,拦截器只能实现其中一种操作,如何才能让拦截器拦截同一个方法时,实现不同的 redis 操作呢?

有以下几个办法

  1. 方法的参数里添加一个专门的变量,用来告诉拦截器做何种操作

  2. 复制该方法为另一个方法,2 个方法作用一样,注解也一样,区别是注解的 action 属性不同

  3. 同上,但是通过方法名来区别

经过考虑,最终采用了第 2 种办法,如下

该接口的实现类如下

2 个方法,都是从数据库里查询栏目下的专辑,但其注解的 action 属性不同,就导致一个用于从缓存查询数据,加快访问速度;另一个则适合于用 db 里的数据刷新 redis 的场合

总结

在 spring 配置文件里开启注解式 aop,使用如下配置

自定义注解,如下2个元注解必须

针对自定义的注解编写拦截器代码并配置拦截器,注意如下几个注解的使用

Last updated