redis返回的结果是null_Spring AOP 用注解封装 redis 缓存
前言
面试时问到用没用过 AOP,很多回答都是用 AOP 做过日志统一处理。 给人感觉就是没做过啊
今天介绍一个用注解封装 redis 缓存的 AOP 实战
redis 缓存加速的基本逻辑
用 redis 加速数据库访问,一般会写出如下代码
@Servicepublic class UserServiceImpl implements UserService { private final UserMapper userMapper; private final RedisClient redisClient; @Autowired public UserServiceImpl(UserMapper userMapper, RedisClient redisClient) { this.userMapper = userMapper; this.redisClient = redisClient; } @Override public User get(Long id) { String key = String.format("USER:%d", id); String value = redisClient.get(key); if (StringUtils.isNotEmpty(value)) { return JSON.parseObject(value, User.class); } User user = userMapper.get(id); if (user != null) { redisClient.set(key, JSON.toJSONString(user)); } return user; }}
其实现逻辑如下
根据输入参数构造 redis 的 key
从 redis 里获取该 key 的值
如果值不为空,则命中缓存,直接返回
redis 未命中
穿透到数据库查询
如果数据库查询到该值,缓存到 redis
返回
实际上用 redis 缓存加速数据库查询基本都是这样的套路,如果在每个 service 都要这样写一遍,太繁琐了,用注解来封装一下吧
思路
通过 Spring 的 AOP 技术,拦截从数据库查询的方法,在从数据库获取结果之前,先从 redis 获取,如果 redis 命中,则直接返回;否则就继续执行从数据库获取的方法,将返回值缓存到 reids 并返回
实际上不限于从数据库获取结果,如果是从远程服务获取值,也可以采用同样的思路
实战步骤
step1:标记要拦截的方法
很显然用注解是个不错的主义,定义如下注解
/** * 这个标注用来为redis的通用化存取设定参数 */@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface Redis { /** 非null值 默认过期时间 **/ int DEFAULT_TTL = 4 * 60 * 60 + 10 * 60; /** null值 默认过期时间 **/ int NULL_TTL = 5 * 60; /** redsi key,见 {@link RedisKeys} **/ String value(); /** *
* 指示方法的哪些参数用来构造key,及其顺序(编号由0开始) * * 示例 * keyArgs = {1,0,2},表示用方法的第二,第一,第三个参数,按顺序来构造key * * 默认值的意思是方法的前 n 个参数来构造key,n 最大为10 * 这样如果构造 key 的参数不多于 10 个且顺序也和方法参数一致,则可以用默认值 *
*/
int[] keyArgs() default { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
/** 执行何种操作,默认是先访问 redis **/
RedisAction action() default RedisAction.REDIS_FIRST;
/** 过期时间,默认250分钟 **/
int ttl() default DEFAULT_TTL;
/** 是否以同步的方式操作redis,默认是false **/
boolean sync() default false;
/** 是否要缓存 null 值,默认为true **/
boolean cacheNull() default true;
/** 如果要缓存null值,过期时间是多少,默认5分钟 **/
int nullTtl() default NULL_TTL;
}
public enum RedisAction {
// 优先从 redis 获取
REDIS_FIRST,
// 穿透 redis 到 db 获取
STAB_REDIS;
}
这个注解用在需要拦截的方法上,还附带了一些元信息
接下来是在目标方法上使用注解
@Servicepublic class UserServiceImpl2 implements UserService { private final UserMapper userMapper; @Autowired public UserServiceImpl2(UserMapper userMapper) { this.userMapper = userMapper; } @Redis("USER:%d") @Override public User get(Long id) { return userMapper.get(id); }}
现在代码就很简洁明了
step2:编写拦截器
@Aspect@Componentpublic class RedisInterceptor { private static final Logger LOG = LoggerFactory.getLogger(RedisInterceptor.class); @Resource private RedisClient redisClient; @Around("@annotation(redis)") public Object doAround(ProceedingJoinPoint pjp, Redis redis) throws Throwable { MethodSignature signature = (MethodSignature) pjp.getSignature(); Method method = signature.getMethod(); // 是否穿透 redis boolean stab = redis.action() == RedisAction.STAB_REDIS; Object[] keyArgs = getKeyArgs(pjp.getArgs(), redis.keyArgs()); String key = keyArgs == null ? redis.value() : String.format(redis.value(), keyArgs); Class> returnType = method.getReturnType(); Object result = stab ? null : get(key, returnType); if (result == null) { result = pjp.proceed(); if (result != null) { setex(key, redis.ttl(), result, redis.sync()); } else if (redis.cacheNull()) { setex(key, redis.nullTtl(), result, redis.sync()); } } return result; } /** 获取构造 redis 的 key 的参数数组 */ private Object[] getKeyArgs(Object[] args, int[] keyArgs) { Object[] redisKeyArgs; int len = keyArgs.length; if (len == 0) { return null; } else { len = min(len, args.length); redisKeyArgs = new Object[len]; int i = 0; for (int n : keyArgs) { redisKeyArgs[i++] = args[n]; if (i >= len) { break; } } return redisKeyArgs; } } private int min(int i, int j) { return i > j ? j : i; } private void setex(final String key, final int ttl, final T data, boolean sync) { try { redisClient.setex(key, ttl, data, sync); } catch (Exception e) { LOG.error("redis set error:{}", e.getMessage(), e); } } private T get(String key, Class clazz) { try { return redisClient.get(key, clazz); } catch (Exception e) { LOG.error("redis get error:{}", e.getMessage(), e); return null; } }}
step3:配置拦截器
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <aop:aspectj-autoproxy proxy-target-class="false" />beans>
这样就 ok 了,在你想要使用 redis 缓存加速的方法上加上 redis 注解吧
关于 redis 穿透
通常情况下,都是优先从 redis 里查询结果。但也有时候需要穿透 redis,到 db 里去获取结果的。
@Redis 注解也支持这种操作,只需要设置 action 属性为 STAB_REDIS 即可
但是,同一个方法,action 要么是 STAB_REDIS,要么是 REDIS_FIRST,拦截器只能实现其中一种操作,如何才能让拦截器拦截同一个方法时,实现不同的 redis 操作呢?
有以下几个办法
方法的参数里添加一个专门的变量,用来告诉拦截器做何种操作
复制该方法为另一个方法,2个方法作用一样,注解也一样,区别是注解的 action 属性不同
两个不同名的方法,实际上代码是一样的
经过考虑,最终采用了第2种办法
总结
在 Spring 配置文件里开启注解式 AOP,使用如下配置
class="false" />
自定义注解,如下 2 个元注解必须
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)
针对自定义的注解编写拦截器代码
拦截器要用到反射,泛型啥的,一切皆有可能
拦截器配置,注意如下几个注解的使用
@Aspect@Component@Around("@annotation(******)")
redis返回的结果是null_Spring AOP 用注解封装 redis 缓存相关推荐
- 从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之十一 || AOP自定义筛选,Redis入门 11.1...
大神留步 先说下一个窝心的问题,求大神帮忙,如何在Task异步编程中,使用Redis存.取Task<List<T>>泛型,有偿帮助,这里谢谢,文末有详细问题说明,可以留言或者私 ...
- redis-----07-----redigo基本命令操作(主要讲如何让go的struct、map展开成redis的参数,以及使用struct获取redis返回的key-value批量数组)
1 请求回应模式 redis 与 client 之间采用请求回应模式,一个请求包对应一个回应包.但是也有例外,pub/sub 模式下,client 发送 subscribe 命令并收到回应包后,之后被 ...
- 搞懂分布式技术14:Spring Boot使用注解集成Redis缓存
版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/a724888/article/details/80785403 为了提高性能,减少数据库的压力,使用 ...
- 5.概念(maven,ssm,springMvc,spring,自定义注解,二级缓存,范式,事务,mysql,线程池,map,hashmap,redis,饿汉,懒汉)
maven是啥: 1.Maven是一个项目管理和综合工具.Maven提供了开发人员构建一个完整的生命周期框架. 创建-导入jar报–编写配置文件-实现业务功能-测试-发布上线. 2.开发团队可以自动完 ...
- Spring Aop 常见注解和执行顺序
欢迎关注方志朋的博客,回复"666"获面试宝典 来源:juejin.cn/post/7062506923194581029 Spring 一开始最强大的就是 IOC / AOP 两 ...
- 扩展基于注解的spring缓存,使缓存有效期的设置支持方法级别-redis篇
2019独角兽企业重金招聘Python工程师标准>>> 这里用的spring对redis的封装spring-data-redis,主要是对RedisCacheManager做一个二次 ...
- Redis 在CentOS 6上的 安装和部署以及redis的主从复制sentinel实现HA
一.简介 Redis是基于内存的存储,所有数据都工作与内存中,基于键值存储(key-value store),经常拿来跟memcached做比较:memcached没有持久能力,Redis有持久能力, ...
- redis序列化_实例讲解Springboot以Template方式整合Redis及序列化问题
1 简介 之前讲过如何通过Docker安装Redis,也讲了Springboot以Repository方式整合Redis,建议阅读后再看本文效果更佳: (1) Docker安装Redis并介绍漂亮的可 ...
- Redis笔记系列(特别总结篇)——常见配置redis.conf知识点总结
2019独角兽企业重金招聘Python工程师标准>>> 由于前几篇说的redis配置信息量有点杂,很多是循序渐进把各个点引出的,不太方便我自己和其他小伙伴日后拿出来瞄一眼,所以,本文 ...
最新文章
- vmware虚拟机移植带来的问题
- RNN 怎么用?给初学者的小教程
- 深度学习入门笔记系列(三)——感知器模型和 tensorboard 的使用方法
- JAVA15.JDK15新特性.4 TextBlock
- PAT天梯赛L3-004 肿瘤诊断
- ant vue 语言_Ant Design Vue是什么
- ARM MMU工作原理剖析[转]
- 前端学习(2705):重读vue电商网站26之路由导航守卫控制访问权限
- ajax实现局部删除,Express+AdminLTE+hbs+Ajax实现局部刷新终极版(第二部分)
- Socket相关操作超时
- android开源2016_2016年开源年鉴:现在提供印刷版
- java程序设计_Java程序设计--接口interface(笔记)
- WMS仓储管理系统实施时要注意哪些事项?
- [fastjson] - fastjson中 JSONObject 和 JSONArray
- 之前8年都在上班工资16000,厌倦了天天上班的日子,就裸辞了。现在很迷茫,下一步怎么办?
- 12.1-12.5 LNMP架构介绍 , MySQL安装 , PHP安装, Nginx介绍
- 基于python下django框架 实现闲置物品二手跳蚤市场交易系统详细设计
- 吴恩达---机器学习的流程(持续更新)
- xml 中的 大于号,小于号
- 从I到R:人工智能语言简史
热门文章
- Android开源库集合(UI效果)
- GitLab 8.9 新增文件锁 和 U2F硬件支持
- android 网络gif_Android SurfaceView实现GIF动画架包,播放GIF动画
- qt qstandarditemmodel rowcount获取行数不正确_MIL+QT实践教程十
- 如何新建Spring Boot工程
- ip登陆异常 php,PHP实例:PHP制作登录异常ip检测功能的实例代码
- zookeeper的acl权限控制_zookeeper权限acl与四字命令
- python输入文字字符串、如何提取字符_如何使用python从字符串中提取url?
- python采用函数式编程模式-浅谈Python 函数式编程
- labimage 怎样旋转图片_隔断墙见多了,头次见能180旋转任意移动,还多出一面墙来储物...