springboot自定义日志注解实现日志管理

springBoot,AOP切面实现日志记录,自定义注解,注解属性动态传参
注解属性动态传参,包括:方法入参传参、方法体传参
方法入参传参,针对链接携带的参数进行记录
方法体传参,对处理后的数据进行记录

1、遇到的问题

1)Caused by: java.lang.IllegalArgumentException: error at ::0 formal unbound in pointcut

在这里插入图片描述
解决方案,把两个入参的位置换一下就好了
在这里插入图片描述

2、自定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotation {
    String operation() default "";
    String description() default "";
}

JDK中有一些元注解,主要有@Target,@Retention,@Document,@Inherited用来修饰注解。
@Target 表明该注解可以应用的java元素类型

Target类型描述
ElementType.TYPE应用于类、接口(包括注解类型)、枚举
ElementType.TYPE应用于类、接口(包括注解类型)、枚举
ElementType.FIELD应用于属性(包括枚举中的常量)
ElementType.METHOD应用于方法
ElementType.PARAMETER应用于方法的形参
ElementType.CONSTRUCTOR应用于构造函数
ElementType.LOCAL_VARIABLE应用于局部变量
ElementType.ANNOTATION_TYPE应用于注解类型
ElementType.PACKAGE应用于包
ElementType.TYPE_PARAMETER1.8版本新增,应用于类型变量)
ElementType.TYPE_USE1.8版本新增,应用于任何使用类型的语句中(例如声明语句、泛型和强制转换语句中的类型)

@Retention 表明该注解的生命周期

生命周期类型描述
RetentionPolicy.SOURCE编译时被丢弃,不包含在类文件中
RetentionPolicy.CLASSJVM加载时被丢弃,包含在类文件中,默认值
RetentionPolicy.RUNTIME由JVM 加载,包含在类文件中,在运行时可以被获取到

@Document 表明该注解标记的元素可以被Javadoc 或类似的工具文档化
@Inherited 表明使用了@Inherited注解的注解,所标记的类的子类也会拥有这个注解

3、aop切面(aspect)

1) 静态参数

①、切面代码

package com.meirit.dong.aspect;

import com.meirit.dong.annotation.LogAnnotation;
import com.meirit.dong.entity.LogEntity;
import com.meirit.dong.mapper.LogMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;

@Slf4j
@Aspect
@Component
public class LogAspect {
    @Autowired
    private LogMapper logMapper;


    @Pointcut("@annotation(logger)")
    public void logAnnotation(LogAnnotation logger) {
    }

    @Around("logAnnotation(logger)")
    public Object around(ProceedingJoinPoint joinPoint, LogAnnotation logger) throws Throwable {
        //获取方法参数值数组
        Object[] args = joinPoint.getArgs();
        //得到其方法签名
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();

        // 记录日志
        saveLog(method);

        //获取方法参数类型数组
        Class[] paramTypeArray = methodSignature.getParameterTypes();
        //动态修改其参数
        //注意,如果调用joinPoint.proceed()方法,则修改的参数值不会生效,必须调用joinPoint.proceed(Object[] args)
        args[1] = 30; // 将num的值设置成30
        Object result = joinPoint.proceed(args);
        return result;
    }

    private void saveLog(Method method) {
        LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);
        LogEntity logEntity = new LogEntity();
        logEntity.setId(UUID.randomUUID().toString());
        logEntity.setOperation(logAnnotation.operation());
        logEntity.setDescription(logAnnotation.description());
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        logEntity.setCreateTime(sf.format(new Date()));
        logMapper.insertLog(logEntity);
    }
}

②、具体应用

 @GetMapping("/test1")
    @ResponseBody
    @LogAnnotation(operation = "查询", description = "查询人员信息")
    public String test1(String name, int num) throws IOException {
        return "success" + num;
    }

③、获取注解内容的2种方式

  • 通过入参获取
    在这里插入图片描述

  • 通过自带的属性获取

 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
 Method method = methodSignature.getMethod();
 LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);
2) 动态传参

动态传参的两个核心:

① 获取通配符
② 获取通配符的值

通配符的定义如下

    /**
    *#{operation} : operation字段的通配符
    *#{description}:description字段的通配符
    * 注意:通配符的设置可自己定义,怎么定义解析的时候怎么解析
    */
    @GetMapping("/test1")
    @ResponseBody
    @LogAnnotation(operation = "#{operation}", description = "#{description}")
    public String test1(String operation, int num) throws IOException {
        AnnotationResolver.INSTANCE.params.put("description","查询人员信息");
        return "success" + num;
    }

注意:上面代码中,传递参数的方式有2种;
第一种:直接通过入参传递,如operation
第二种:在方法体里面传递,如description

代码实现如下

①、切面代码

package com.meirit.dong.aspect;

import com.meirit.dong.annotation.LogAnnotation;
import com.meirit.dong.aspect.resolver.AnnotationResolver;
import com.meirit.dong.entity.LogEntity;
import com.meirit.dong.mapper.LogMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;

@Slf4j
@Aspect
@Component
public class LogAspect {
    @Autowired
    private LogMapper logMapper;


    @Pointcut("@annotation(com.meirit.dong.annotation.LogAnnotation)")
    public void logAnnotation() {
    }

    @Around("logAnnotation()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //获取方法参数值数组
        Object[] args = joinPoint.getArgs();
        //动态修改其参数
        //注意,如果调用joinPoint.proceed()方法,则修改的参数值不会生效,必须调用joinPoint.proceed(Object[] args)
        args[1] = 30; // 将num的值设置成30
        Object result = joinPoint.proceed(args);

        //得到其方法签名
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        //获取方法参数类型数组
        //Class[] paramTypeArray = methodSignature.getParameterTypes();

        // 记录日志
        saveLog(method,joinPoint);
        return result;
    }

    private void saveLog(Method method, ProceedingJoinPoint joinPoint) {
        LogAnnotation logAnnotation = method.getAnnotation(LogAnnotation.class);
        LogEntity logEntity = new LogEntity();
        logEntity.setId(UUID.randomUUID().toString());
        Object operation = AnnotationResolver.INSTANCE.resolver(joinPoint, logAnnotation.operation());
        Object description = AnnotationResolver.INSTANCE.resolver(joinPoint, logAnnotation.description());
        logEntity.setOperation(String.valueOf(operation));
        logEntity.setDescription(String.valueOf(description));
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        logEntity.setCreateTime(sf.format(new Date()));
        logMapper.insertLog(logEntity);
    }
}

② 解析通配符代码

package com.meirit.dong.aspect.resolver;

import graphql.com.google.common.collect.Maps;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;
import java.util.Map;

public enum AnnotationResolver {
    INSTANCE;
    public Map<String,Object> params = Maps.newConcurrentMap();
    public Object resolver(JoinPoint joinPoint, String str) {
        if (str == null) {
            return null ;
        }

        Object value = null;
        if (str.matches("#\\{\\D*\\}")) {// 如果name匹配上了#{},则把内容当作变量
            String newStr = str.replaceAll("#\\{", "").replaceAll("\\}", "");
            if (newStr.contains(".")) { // 复杂类型
                try {
                    value = complexResolver(joinPoint, newStr);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                value = simpleResolver(joinPoint, newStr);
            }
        } else { //非变量
            value = str;
        }
        return value;
    }
    private Object complexResolver(JoinPoint joinPoint, String str) throws Exception {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

        String[] names = methodSignature.getParameterNames();
        Object[] args = joinPoint.getArgs();
        String[] strs = str.split("\\.");

        for (int i = 0; i < names.length; i++) {
            if (strs[0].equals(names[i])) {
                Object obj = args[i];
                Method dmethod = obj.getClass().getDeclaredMethod(getMethodName(strs[1]), null);
                Object value = dmethod.invoke(args[i]);
                return getValue(value, 1, strs);
            }
        }
        return null;
    }

    private Object getValue(Object obj, int index, String[] strs) {
        try {
            if (obj != null && index < strs.length - 1) {
                Method method = obj.getClass().getDeclaredMethod(getMethodName(strs[index + 1]), null);
                obj = method.invoke(obj);
                getValue(obj, index + 1, strs);
            }
            return obj;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private String getMethodName(String name) {
        return "get" + name.replaceFirst(name.substring(0, 1), name.substring(0, 1).toUpperCase());
    }

    private Object simpleResolver(JoinPoint joinPoint, String str) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        String[] names = methodSignature.getParameterNames();
        Object[] args = joinPoint.getArgs();
        Object value = null;
        if(params.keySet().contains(str)){
            value = params.get(str);
            params.remove(str);
        }else {
            for (int i = 0; i < names.length; i++) {
                if (str.equals(names[i])) {
                    value = args[i];
                    break;
                }
            }
        }
        return value;
    }

}

③、业务实现

    @GetMapping("/test1")
    @ResponseBody
    @LogAnnotation(operation = "#{operation}", description = "#{description}")
    public String test1(String operation, int num) throws IOException {
        AnnotationResolver.INSTANCE.params.put("description","查询人员信息");
        return "success" + num;
    }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SpringBoot中可以自定义注解实现特定的功能。自定义注解的步骤如下: 1. 使用`@interface`关键字来定义注解,可以在注解中设置属性。 2. 可以通过注解的属性来传递参数,比如设置注解中的属性值。 3. 可以通过判断某个类是否有特定注解来进行相应的操作。 在SpringBoot中,自定义注解可以用于实现日志记录、定时器等功能。通过使用注解,可以简化代码,并提高开发效率。同时,自定义注解也是Spring框架中广泛应用的一种方式,可以在SpringMVC框架中使用注解来配置各种功能。而在SpringBoot框架中,更是将注解的使用推向了极致,几乎将传统的XML配置都替换为了注解。因此,对于SpringBoot来说,自定义注解是非常重要的一部分。123 #### 引用[.reference_title] - *1* *3* [springboot 自定义注解(含源码)](https://blog.csdn.net/yb546822612/article/details/88116654)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}} ] [.reference_item] - *2* [SpringBoot-自定义注解](https://blog.csdn.net/weixin_44809337/article/details/124366325)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值