zoukankan      html  css  js  c++  java
  • 【Java】使用动态代理输出RedisTemplate命令执行日志

    一. 说明

    1. 功能:在 redisTemplate 命令执行前后 输出日志

    2. 原理

    1. 作用是输出 RedisTemplate 命令执行日志。包括:命令名称、参数、返回值等
    2. 原理是使用动态代理拦截类 redisConnectionFactory 的 getConnection() 方法,监控Redis命令
    3. 目前主要输出 get/set/pExpire/pSetEx 等命令日志,可根据需要扩展
    1. 示例
    • 单元测试
    @Test
    public void RedisTemplateTest() {
    
        String key = "testTemplate:1";
    
        this.redisTemplate.opsForValue().set(key, "testTemplatevalue1", 1000L, TimeUnit.MILLISECONDS);
    
        String value = this.redisTemplate.opsForValue().get(key);
    
    }
    
    • 日志输出
    // 输出日志
    // 参数分别为:key、超时时间、value
    Redis Log: method:pSetEx params:["testTemplate:1","1000","testTemplatevalue1"] result:
    
    // 参数分别为:key 返回结果:result
    Redis Log: method:get params:["testTemplate:1"] result:testTemplatevalue1
    

    二. 源码

    import com.alibaba.fastjson.JSON;
    import lombok.Data;
    import lombok.extern.slf4j.Slf4j;
    import org.aopalliance.intercept.MethodInterceptor;
    import org.aopalliance.intercept.MethodInvocation;
    import org.springframework.aop.framework.ProxyFactory;
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.core.RedisTemplate;
    
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.ObjectOutputStream;
    import java.lang.reflect.Method;
    
    // https://blog.csdn.net/youbl/article/details/109266634
    // https://github.com/youbl/study/tree/master/demo-log-redis
    
    /**
     * 本类的作用是输出 RedisTemplate 命令执行日志。包括:命令名称、参数、返回值等
     * 原理是使用动态代理拦截类 redisConnectionFactory 的 getConnection() 方法,监控Redis命令
     * 目前主要输出 get/set/pExpire/pSetEx 等命令日志,可根据需要扩展
     */
    @Configuration
    @Slf4j
    public class RedisCommandLogBean implements BeanPostProcessor {
    
        @Override
        public Object postProcessBeforeInitialization(Object o, String s) throws BeansException {
    
            return o;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) {
    
            if (beanName.equals("redisConnectionFactory")) {
    
                // 使用代理,覆盖原Redis工厂类
                return getProxy(bean, invocation -> this.interceptorRedisFactory(invocation));
    
            } else if (bean instanceof RedisTemplate) {
    
            }
            return bean;
        }
    
        // 使用 ProxyFactory 类生成动态代理方法
        public static Object getProxy(Object obj, MethodInterceptor interceptor) {
    
            ProxyFactory proxy = new ProxyFactory(obj);
            proxy.setProxyTargetClass(true);
            proxy.addAdvice(interceptor);
            return proxy.getProxy();
        }
    
        // 如果是getConnection方法,则对该方法进行代理
        public Object interceptorRedisFactory(MethodInvocation invocation) throws Throwable {
    
            Object ret = invocation.proceed();
            String methodName = invocation.getMethod().getName();
            if (methodName.equals("getConnection")) {
                return getProxy(ret, this::interceptorGetConnection);
            }
            return ret;
        }
    
        private Object interceptorGetConnection(MethodInvocation invocation) throws Throwable {
    
            Method method = invocation.getMethod();
            String name = method.getName();
            if (name.equals("isPipelined") || name.equals("close"))
                return invocation.proceed();
    
            RedisDto redisDto = new RedisDto();
            redisDto.setMethodName(name);
    
            Object target = invocation.getThis();
            redisDto.setClassName(target.getClass().getName());
    
            Object[] args = invocation.getArguments();
    
            // 方法名(命令参数)
            String commandName = invocation.getMethod().getName();
    
            if (this.containMethod(commandName)) {
    
                String[] argsStr = new String[args.length];
                for (int i = 0; i < args.length; i++) {
    
                    // 字节数组类型
                    if (args[i] instanceof byte[]) {
    
                        String s = new String(toByteArray(args[i]));
                        
                        // 有乱码,乱码中包含字符串"xp",原因不明。这里删除乱码
                        if (s.indexOf("xp") > 0) {
                            
                            s = s.substring(s.indexOf("xp") + 2).trim(); 
                            
                        }
    
                        argsStr[i] = s;
                    } else {
                        argsStr[i] = args[i].toString();
                    }
    
                }
                redisDto.setParams(JSON.toJSONString(argsStr));
            }
    
            Object ret = null;
            long start = System.currentTimeMillis();
    
            try {
    
                ret = invocation.proceed();
    
                String result = new String(toByteArray(ret));
                
                // 有乱码,乱码中包含字符串"xp",原因不明。这里删除乱码
                if (result.indexOf("xp") > 0) {
                    result = result.substring(result.indexOf("xp") + 2).trim(); 
                }
    
                redisDto.setResult(result);
    
                return ret;
            } catch (Exception ex) {
    
                redisDto.setEx(ex);
                throw ex;
    
            } finally {
    
                redisDto.setExecuteTime(System.currentTimeMillis() - start);
    
                if (this.containMethod(commandName))
    
                    log.info("Redis Log: method:{} params:{} result:{}",
                            redisDto.getMethodName(), redisDto.getParams(), redisDto.getResult());
            }
        }
    
        // 监控的命令
        private boolean containMethod(String commandName) {
    
            if (commandName.equalsIgnoreCase("get")
                    || commandName.equalsIgnoreCase("set")
                    || commandName.equalsIgnoreCase("pExpire")
                    || commandName.equalsIgnoreCase("pSetEx")) {  // 对应方法:ValueOperations --> set(K key, V value, long timeout, TimeUnit unit)
    
                return true;
            }
    
            return false;
        }
    
        public byte[] toByteArray(Object obj) {
    
            byte[] bytes = null;
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            try {
                ObjectOutputStream oos = new ObjectOutputStream(bos);
                oos.writeObject(obj);
                oos.flush();
                bytes = bos.toByteArray();
                oos.close();
                bos.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            return bytes;
        }
    
    
        @Data
        public class RedisDto {
    
            private String className;
    
            private String methodName;
    
            private String params;
    
            private Object result;
    
            private long executeTime;
    
            private String remark;
    
            private Exception ex;
        }
    }
    
  • 相关阅读:
    调试WEB APP多设备浏览器
    Android病毒家族及行为(一)
    如何判断Android设备是否为模拟器
    python操作MongoDB
    python面试题大全(二)
    白话经典算法系列之——快速排序
    白话经典算法系列之——希尔排序的实现
    白话经典算法系列之——直接插入排序的三种实现
    白话经典算法系列之——冒泡排序的三种实现(转)
    MySQL 数据库赋予用户权限操作表
  • 原文地址:https://www.cnblogs.com/gossip/p/14517095.html
Copyright © 2011-2022 走看看