相关背景及资源:html
曹工说Spring Boot源码(1)-- Bean Definition究竟是什么,附spring思惟导图分享java
曹工说Spring Boot源码(2)-- Bean Definition究竟是什么,我们对着接口,逐个方法讲解git
曹工说Spring Boot源码(3)-- 手动注册Bean Definition不比游戏好玩吗,咱们来试一下redis
曹工说Spring Boot源码(4)-- 我是怎么自定义ApplicationContext,从json文件读取bean definition的?spring
曹工说Spring Boot源码(5)-- 怎么从properties文件读取beansql
曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的数据库
曹工说Spring Boot源码(7)-- Spring解析xml文件,到底从中获得了什么(上)json
曹工说Spring Boot源码(8)-- Spring解析xml文件,到底从中获得了什么(util命名空间)mybatis
曹工说Spring Boot源码(9)-- Spring解析xml文件,到底从中获得了什么(context命名空间上)ide
曹工说Spring Boot源码(10)-- Spring解析xml文件,到底从中获得了什么(context:annotation-config 解析)
曹工说Spring Boot源码(11)-- context:component-scan,你真的会用吗(此次来讲说它的奇技淫巧)
曹工说Spring Boot源码(12)-- Spring解析xml文件,到底从中获得了什么(context:component-scan完整解析)
曹工说Spring Boot源码(13)-- AspectJ的运行时织入(Load-Time-Weaving),基本内容是讲清楚了(附源码)
曹工说Spring Boot源码(14)-- AspectJ的Load-Time-Weaving的两种实现方式细细讲解,以及怎么和Spring Instrumentation集成
曹工说Spring Boot源码(15)-- Spring从xml文件里到底获得了什么(context:load-time-weaver 完整解析)
曹工说Spring Boot源码(16)-- Spring从xml文件里到底获得了什么(aop:config完整解析【上】)
曹工说Spring Boot源码(17)-- Spring从xml文件里到底获得了什么(aop:config完整解析【中】)
曹工说Spring Boot源码(18)-- Spring AOP源码分析三部曲,终于快讲完了 (aop:config完整解析【下】)
曹工说Spring Boot源码(19)-- Spring 带给咱们的工具利器,建立代理不用愁(ProxyFactory)
工程结构图:
本篇是独立的,和前面几篇aop相关分析没有特别关联,可是使用了上一篇提到的工具类。
曹工说Spring Boot源码(19)-- Spring 带给咱们的工具利器,建立代理不用愁(ProxyFactory)
以前也使用相似的思路,实现过完整sql日志记录。
曹工杂谈--使用mybatis的同窗,进来看看怎么在日志打印完整sql吧,在数据库可执行那种
这两天在搬砖,有个需求,是统计类的。通常来讲,统计类的东西,好比要统计:用户总数,用户的新增总数,当天每一个小时为维度的新增数量,各个渠道的新增用户数量;这些,可能都得在redis里维护,而后某个用户注册时,去把全部这些redis结构+1。
但这种代码,通常入口不少,修改这些值的地方不少,编码时很容易发生遗漏,或者编码错误,致使最后统计数据不许确。数据不许确,固然是bug,问题是,这种bug还很差排查。
若是可以记录下redis操做日志就行了。
如下,是我已经实现的效果,这是一次请求中的一次redis操做,能够看到,是put方法。
咱们用的是spring boot 2.1.7,直接集成的RedisTemplate。固然,只要是使用RedisTemplate便可,和spring boot没多大关系。
我看了下咱们平时是怎么去操做redis 的hash结构的,大概代码以下:
@Autowired @Qualifier("redisTemplate") private RedisTemplate<String,Object> redisTemplate; HashOperations<String, HK, HV> ops = redisTemplate.opsForHash(); ops.put(key, hashKey,fieldValue);
通常就是,先经过opsForHash,拿到HashOperations,再去操做hash结构。
我如今的想法就是,在执行相似ops的put的方法以前,把那几个参数记录到日志里。
要想让ops记录咱们的日志,咱们只能拦截其每一个方法,这一步就得使用一个代理对象,去替换掉真实的对象。
可是,怎么才能让redisTemplate.opsForHash()返回的ops,是咱们代理过的对象呢?
因此,这一步,还得在生成redisTemplate的地方下功夫,让其生成一个redisTemplate的代理对象,这个代理对象,拦截opsForHash方法。
总结下,须要作两件事:
@Configuration public class RedisConfig { @Bean public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String,Object> template = new RedisTemplate<>(); template.setValueSerializer(new CustomGenericJackson2JsonRedisSerializer()); template.setHashKeySerializer(new CustomHashKeyRedisSerializer()); template.setKeySerializer(RedisSerializer.string()); template.setHashValueSerializer(new CustomGenericJackson2JsonRedisSerializer()); template.setConnectionFactory(redisConnectionFactory); return template; }
@Bean public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String,Object> template = new RedisTemplate<>(); template.setValueSerializer(new CustomGenericJackson2JsonRedisSerializer()); template.setHashKeySerializer(new CustomHashKeyRedisSerializer()); template.setKeySerializer(RedisSerializer.string()); template.setHashValueSerializer(new CustomGenericJackson2JsonRedisSerializer()); template.setConnectionFactory(redisConnectionFactory); ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setTarget(template); proxyFactory.setProxyTargetClass(true); proxyFactory.addAdvice(new MethodInterceptor() { @Override public Object invoke(MethodInvocation invocation) throws Throwable { //拦截opsForHash boolean b = invocation.getMethod().getName().equals("opsForHash"); if (b) { // todo,下一步再完善这里 } return invocation.proceed(); } }); //这里获取到针对template的代理对象,并返回 Object proxy = proxyFactory.getProxy(); return (RedisTemplate<String, Object>) proxy; }
你们能够仔细看上面的代码,利用了前一讲咱们学习了的ProxyFactory,来生成代理;使用它呢,比较方便,不用管底层它是用jdk动态代理,仍是cglib代理,spring已经帮咱们处理好了。
总之,上面这段,就是把redisTemplate给换了。咱们具体要在拦截了opsForHash里,作什么动做呢?咱们再看。
@Bean public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String,Object> template = new RedisTemplate<>(); template.setValueSerializer(new CustomGenericJackson2JsonRedisSerializer()); template.setHashKeySerializer(new CustomHashKeyRedisSerializer()); template.setKeySerializer(RedisSerializer.string()); template.setHashValueSerializer(new CustomGenericJackson2JsonRedisSerializer()); template.setConnectionFactory(redisConnectionFactory); ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setTarget(template); proxyFactory.setProxyTargetClass(true); proxyFactory.addAdvice(new MethodInterceptor() { @Override public Object invoke(MethodInvocation invocation) throws Throwable { boolean b = invocation.getMethod().getName().equals("opsForHash"); if (b) { // 1. 这一步,拿到原有的opsForHash的返回结果 HashOperations hashOperations = (HashOperations) invocation.proceed(); //2. 下边,对hashOperations进行代理 ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.setTarget(hashOperations); proxyFactory.setProxyTargetClass(false); proxyFactory.setInterfaces(HashOperations.class); //3. 咱们这个代理干什么事呢,就是加了一个方法前的拦截器,记录日志 proxyFactory.addAdvice(new MethodBeforeAdviceInterceptor(new MethodBeforeAdvice() { // 使用fastjson格式化了参数,并记录到日志 @Override public void before(Method method, Object[] args, Object target) { log.info("method:{},args:{}",method.getName(), JSON.toJSONString(args, SerializerFeature.PrettyFormat)); } })); // 这里返回针对hashOperations的代理 return proxyFactory.getProxy(); } return invocation.proceed(); } }); Object proxy = proxyFactory.getProxy(); return (RedisTemplate<String, Object>) proxy; }
我这个拦截比较粗,如今是把get类的日志也打出来了。你们能够判断下method的名称,来自行过滤掉。
ok,本篇先到这里。下讲继续讲Spring ProxyFactory的内容。