美文网首页
利用Spring AOP实现数据埋点-1

利用Spring AOP实现数据埋点-1

作者: 大黑跟小白的日常 | 来源:发表于2020-08-10 15:03 被阅读0次

话不多,直接刚

需求

在某个可以被动态代理的方法上加上某个注解,实现 业务数据 埋点操作;

使用方式如下

枚举类,维护打印日志规则

/**
 * 最多支持打印5个参数
 */
public enum  EBusinessOPT {
    /**
     * 业务线,操作,具体业务 参数 key
     */
    TEST("ORDER","TEST",
            Arrays.asList("userName","userAge","userPhone","testabc"));

    private String optCode;
    private String optDesc;
    private List<String> logContentValueNames;

    EBusinessOPT(String optCode, String optDesc, List<String> logContent) {
        this.optDesc = optDesc;
        this.optCode = optCode;
        this.logContentValueNames = logContent;
    }

    public String getOptCode() {
        return optCode;
    }

    public String getOptDesc() {
        return optDesc;
    }

    public List<String> getLogContent() {
        return logContentValueNames;
    }
}

在目标方法上使用

如果需要使用方法参数作为日志打印目标,则需要如下设置。方法参数顺序跟 使用的 日志规约 枚举对象 的logContentValueNames要有业务对应关系!

    @RequestMapping("/test2")
    @BPoint(key = EBusinessOPT.TEST, valueNames = {"#user.name","#user.age","#user.phone","#testabc"})
    public String test2(User user,String testabc) {
        logger.info("-------------test2---------------");
        return "test2";
    }

结果演示

image.png
image.png

分割线------------------------------------------------------------------------------------------------------------------------------------------------------

具体实现

依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

自定义注解类

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface BPoint {
    EBusinessOPT key();
    String[] valueNames();
}

切面逻辑,这里以before为例,具体用哪种增强方式,可以按需求自行定义

@Aspect
@Component
public class BPointAspect {
    private Logger log = LoggerFactory.getLogger(BPointAspect.class);
    @Pointcut("@annotation(com.fc.nacosdemo.bpoint.annotation.BPoint)")
    public void bpPointCut() {}
    @Before("bpPointCut()")
    public void thirdBindInfoModifyBefore(JoinPoint joinPoint) {
        // 将参数值设置到注解属性上
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        BPoint annotation = signature.getMethod().getAnnotation(BPoint.class);
        String[] valueNames = annotation.valueNames();
        EBusinessOPT key = annotation.key();

        if (valueNames == null || key == null) {
            return;
        }
        Object[] values = new Object[valueNames.length];
        // 获取方法参数名称
        String[] parameterNames = signature.getParameterNames();
        // 将方法参数名称跟对应的值 封装到 map
        Map<String, Object> paramMap = new HashMap<String, Object>();
        for (int i = 0; i < parameterNames.length; i++) {
            String parameterName = parameterNames[i];
            paramMap.put(parameterName, joinPoint.getArgs()[i]);
        }

        for (int i = 0; i < valueNames.length; i++) {
            String vname = valueNames[i];
            if (!vname.startsWith("#")) {
                values[i] = vname;
                continue;
            }
            vname = vname.substring(1, vname.length());
            if (vname.indexOf(".") == -1) {
                //未包含层级
                Object value = paramMap.get(vname);
                if (value == null)
                    log.error("BPoint 配置 出错 ! , Method is {}, {} not find ! , 跟方法参数名称未匹配! ",
                            signature.getMethod().getClass().getName() + ":" + signature.getMethod().getName(), vname);
                values[i] = value;
                continue;
            }
            // 具有层级结构
            String[] names = vname.split("\\.");
            LinkedList<String> strings = new LinkedList<String>();
            for (String name : names) {
                strings.addLast(name);
            }
            strings.removeFirst();
            Object v = getV(paramMap.get(names[0]), strings);
            values[i] = v;
        }
        // 打印日志
        bp(key, values);
    }
    // 递归获取层级参数值
    private Object getV(Object origin, LinkedList<String> names) {
        if(names.size() == 0){
            return origin;
        }
        String fieldName = names.getFirst();

        Class<?> aClass = origin.getClass();
        try {
            Field declaredField = aClass.getDeclaredField(fieldName);
            declaredField.setAccessible(true);
            origin = declaredField.get(origin);//name
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
        names.removeFirst();
        return getV(origin, names);
    }
    // 埋点日志打印,最多支持5个额外业务参数打印
    private void bp(EBusinessOPT eBusinessOPT, Object... values) {
        String date = LocalDate.now().toString();
        String time = LocalTime.now().toString();
        String datetime = date + " " + time;
        List<String> logContent = eBusinessOPT.getLogContent();
        StringBuilder sb = new StringBuilder();
        for (String s2 : logContent) {
            sb.append("\"").append(s2).append("\"").append(":\"{}\",");
        }
        sb.deleteCharAt(sb.length() - 1);
        sb.append("}");
//        log.info("{\"LOGLOG\":\"{}\",\"OPTLOG\":\"{}\",\"DATE-TIME\":\"{}\"," + sb.toString(),
//                eBusinessOPT.getOptCode(), eBusinessOPT.getOptDesc(), datetime, values);
        switch (values.length) {
            case 1:
                log.info("{\"LOGLOG\":\"{}\",\"OPTLOG\":\"{}\",\"DATE-TIME\":\"{}\"," + sb.toString(),
                        eBusinessOPT.getOptCode(), eBusinessOPT.getOptDesc(), datetime, values[0]);
                break;
            case 2:
                log.info("{\"LOGLOG\":\"{}\",\"OPTLOG\":\"{}\",\"DATE-TIME\":\"{}\"," + sb.toString(),
                        eBusinessOPT.getOptCode(), eBusinessOPT.getOptDesc(), datetime, values[0], values[1]);
                break;
            case 3:
                log.info("{\"LOGLOG\":\"{}\",\"OPTLOG\":\"{}\",\"DATE-TIME\":\"{}\"," + sb.toString(),
                        eBusinessOPT.getOptCode(), eBusinessOPT.getOptDesc(), datetime, values[0], values[1], values[2]);
                break;
            case 4:
                log.info("{\"LOGLOG\":\"{}\",\"OPTLOG\":\"{}\",\"DATE-TIME\":\"{}\"," + sb.toString(),
                        eBusinessOPT.getOptCode(), eBusinessOPT.getOptDesc(), datetime, values[0], values[1], values[2], values[3]);
                break;
            case 5:
                log.info("{\"LOGLOG\":\"{}\",\"OPTLOG\":\"{}\",\"DATE-TIME\":\"{}\"," + sb.toString(),
                        eBusinessOPT.getOptCode(), eBusinessOPT.getOptDesc(), datetime, values[0], values[1], values[2], values[3], values[4]);
                break;
        }
    }
}

// 具体业务日志打印 可以用异步优化;
// 结合logback 日志收集 展示 ,待续....

相关文章

  • 利用Spring AOP实现数据埋点-1

    话不多,直接刚 需求 在某个可以被动态代理的方法上加上某个注解,实现 业务数据 埋点操作; 使用方式如下 枚举类,...

  • web填坑-AOP

    AOP面向切面编程 利用Spring注解方式实现AOP功能 student.java StuInterceptor...

  • spring-aop

    aop概念aop概念aop术语AOP实现方式1、spring-aop(使用xml文件实现AOP)2、AspectJ...

  • Spring之AOP面向切面编程

    十、AOP面向切面编程 目录:什么是AOP、AOP在Spring中的作用、Spring实现AOP 1.什么是AOP...

  • Spring-AOP切面编程

    一、概要 Spring加入了对AOP编程支持,利用AOP的思想结合Spring的一些API可以实现核心业务与辅助业...

  • Spring AOP源码02 - 代理的创建

    Spring AOP 的实现 Spring AOP 不同于 AspectJ 的 AOP 实现,是在 runtime...

  • Spring-AOP事物管理

    1.概述 Spring 中的事务主要是利用 Aop 思想,简化事务的配置 2.核心接口 Spring事务管理的实现...

  • Spring 的 AOP 概述和底层实现

    Spring 的 AOP 概述和底层实现 1. 什么是 AOP AOP (Aspect Oriented Prog...

  • Spring AOP(一)

    Spring AOP实现原理 动态代理: 利用核心类Proxy和接口InvocationHandler(基于代理模...

  • AOP无痕埋点技术

    使用AOP实现iOS应用内的埋点计数 - 简书 iOS用户行为追踪——无侵入埋点 - CSDN博客 iOS 无埋点...

网友评论

      本文标题:利用Spring AOP实现数据埋点-1

      本文链接:https://www.haomeiwen.com/subject/vgsddktx.html