相關背景及資源: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的內容。