我们在做应用项目的时候经常会使用redis缓存来保存经常访问的数据。但是数组用jedis就不太好缓存了。
例如应用中可能同时存在多个活动,这些活动在首页中所以经常被查询,于是我就想存到缓存中,如果是mangoDB可能问题就结束了。但是我用的是redis来缓存就困难了,所以我就设计了一个基于切面编程的缓存管理器,使用如下:
1.在查询方法上增加注解,并指定一个key,也可以指定缓存时间。
2.在调用查询方法前,先查询redis是否已有缓存。如果有,则返回缓存内容并更新缓存时间,不查询数据库。如果没有,则执行查询方法,并将查询方法存起来。
3.在更新方法上增加注解,并指定key,以指向刚刚的缓存。
4.执行更新方法后,将缓存删除,迫使下次查询的时候重新缓存,以保证时效性。
引入redis就不讲了,默认大家已经会了。我们现在只需要redis的三个操作:set、delete、expire
如何将redis和springboot组合在一起这里就不提了,在我的其他博客以后会有。现在我们默认是有一个redisUtil可以进行上面三个操作
首先我们要引入切面编程的依赖(redis的依赖就不展示了)
<!-- 切面编程 --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency>
我们先定义两个注解:
查询注解RedisCacheSelect:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.concurrent.TimeUnit; /** * @author 作者:李荣宗 * @email 邮箱:1009512886@qq.com * @company 公司:深圳鼎冠网络科技有限公司 * @project 项目:springboot * @createDate 创建时间:2019/3/8 9:46 * @function 作用 : * 查询缓存的注解,指定缓存的主键key,也可以设置缓存时间(默认5分钟),若查询数据不在缓存中,则执行数据库的查询,然后缓存起来。若在缓存中,则更新过期时间 */ @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) public @interface RedisCacheSelect { // 缓存的主键,通过主键来区分不同缓存 String key(); // 缓存时间量 long time() default 5L; // 缓存时间单位 TimeUnit unit() default TimeUnit.MINUTES; }
更新注解RedisCacheUpdate:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author 作者:李荣宗 * @email 邮箱:1009512886@qq.com * @company 公司:深圳鼎冠网络科技有限公司 * @project 项目:springboot * @createDate 创建时间:2019/3/8 9:46 * @function 作用 : 缓存更新的注解,在注解中指明缓存的主键key,然后再reids中将该缓存删除 */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface RedisCacheUpdate { //缓存的主键 String key(); }
这两个注解核心是key,我们需要以key去辨别是不是我们要操作的缓存,这也是缺点之一,需要额外取名。
然后我们通过一个切面类来执行上面的操作:
import java.util.concurrent.TimeUnit; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.dingguan.template.util.RedisUtils; /** * @author 作者:李荣宗 * @email 邮箱:1009512886@qq.com * @company 公司:深圳鼎冠网络科技有限公司 * @project 项目:springboot * @createDate 创建时间:2019/3/8 9:23 * @function 作用 : redis 缓存切面编程。用于缓存数组列表类, 需要额外命名主键key,通过主键key操作该缓存,key在注解中定义 */ @Aspect @Component public class RedisCacheAspect { @Autowired RedisUtils redisUtils;//我自己写的redis工具,只需要上面三个操作就可以 @Pointcut("@annotation(com.dingguan.*.aspect.RedisCacheSelect)") public void RedisCacheSelect() { } @Pointcut("@annotation(com.dingguan.*.aspect.RedisCacheUpdate)") public void RedisCacheUpdate() { } /* * 缓存查询,首次查询添加到缓存,未过期时更新缓存 */ @Around("RedisCacheSelect()") public Object select(ProceedingJoinPoint joinPoint) throws Throwable { // 获取注解的参数 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); RedisCacheSelect annotation = signature.getMethod().getAnnotation(RedisCacheSelect.class); // redis的主键 String key = annotation.key(); // 保存时间量 Long time = annotation.time(); // 保存时间单位 TimeUnit unit = annotation.unit(); // 查询redis保存的结果 Object res = redisUtils.getObject(key); // 判断redis中是否存在缓存 if (res == null) { // 不存在缓存,只能执行数据库查询,然后保存到缓存 res = joinPoint.proceed(); redisUtils.setObject(key, res, time, unit); return res; } else { // 缓存中有,无需查询数据库,将过期时间延长 redisUtils.expireObject(key, time, unit); return res; } } /* * 更新缓存,为了不影响查询结果的实效性, 实际上是直接删除缓存,迫使redis下次查询时重新缓存,保证时效性 */ @Around("RedisCacheUpdate()") public void update(ProceedingJoinPoint joinPoint) throws Throwable { // 获取注解的参数 MethodSignature signature = (MethodSignature) joinPoint.getSignature(); RedisCacheUpdate annotation = signature.getMethod().getAnnotation(RedisCacheUpdate.class); // redis的主键 String key = annotation.key(); // 从缓存中清楚 redisUtils.deleteObject(key); // 执行数据库修改操作 joinPoint.proceed(); } }
redisUtils中的操作都是使用redisTemplate<String,Object>的。
贴 部分 RedisUtils的代码(还需要RedisConfig配置了才有用):
import java.util.Date; import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.HashOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; /** * @author 作者:李荣宗 * @email 1009512886@qq.com * @createDate 创建时间:2018年9月2日 上午11:19:46 */ @Component public class RedisUtils { @Autowired RedisTemplate<String,Object> objectTemplate; /** * 根据key 查询 * @param key * * @return */ public Object getObject(String key){ Object value = objectTemplate.opsForValue().get(key); return value; } /** * 将对象存入redis 并设置 过期时间 * @param key 键 * @param value 值 * @param time 时间长度 * @param unit 时间单位 */ public void setObject(String key,Object value,Long time,TimeUnit unit){ objectTemplate.opsForValue().set(key,value,time,unit); } /** * 修改过期时间 * @param key 键 * @param date 过期时间 */ public void expireObject(String key,Date date){ Long time = DateUtil.getBetweenSecond(new Date(),date); objectTemplate.expire(key,time,TimeUnit.SECONDS); } /** * 修改过期时间 * @param key 键 * * @param time 时间量 * @param unit 时间单位 */ public void expireObject(String key,Long time,TimeUnit unit){ objectTemplate.expire(key,time,unit); } /** * 删除一个对象 * @param key 键 */ public void deleteObject(String key){ objectTemplate.delete(key); } }
最后我们来个测试方法:
import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import org.springframework.stereotype.Service; /** * @author 作者:李荣宗 * @email 邮箱:1009512886@qq.com * @company 公司:深圳鼎冠网络科技有限公司 * @project 项目:springboot * @createDate 创建时间:2019/3/8 9:28 * @function 作用 : 缓存测试 的接口 */ @Service public class RedisCacheService{ @RedisCacheSelect(key="aa",time=30L,unit= TimeUnit.SECONDS) public Object select() { List<String> strings = new ArrayList<String>(); strings.add("查数据库"); return strings; } @RedisCacheUpdate(key="aa") public void update(Object obejct){ System.out.println("执行修改"); } }
在Controller中调用(这里我用了swagger,如果没有用swagger的把@Api开头的去掉就可以了):
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import com.dingguan.template.aspect.RedisCacheService; import com.dingguan.template.bean.ResultBean; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; /** * @author 作者:李荣宗 * @email 邮箱:1009512886@qq.com * @company 公司:深圳鼎冠网络科技有限公司 * @project 项目:springboot * @createDate 创建时间:2018/12/10 10:52 * @function 作用 : 测试接口 */
@RestController @Api(description = "测试专用接口") @RequestMapping("/test") // @ApiIgnore public class Test { @Autowired RedisCacheService redisCacheService; @GetMapping("/t1") @ApiOperation("测试") public ResultBean select(){ Object res =redisCacheService.select(); return new ResultBean(200,res); } @GetMapping("/t2") @ApiOperation("测试") public ResultBean update(){ redisCacheService.update("ss"); return new ResultBean(200,"s"); }
}