aop实现动态日志

一.简介

​ aop面向切面编程,我们可以使用它来进行方法执行之前做一些事情,也可以在方法之后做一些事情等等。

消息通知类包括:

1.前置通知(before):目标方法运行之前调用;

2.最终通知(after):在目标方法运行之后调用,无论是否正常执行完成,还是抛出异常,都会执行;

3.后置通知(after-returning):在目标方法正常执行之后执行,如果出现异常,则不会执行;

4.异常拦截通知(after-throwing):在目标方法运行之后,并且出现异常就执行;

5.环绕通知(around):在目标方法执行之前和之后都会调用。

基于aop的这种特性,我们可以用它来做一些全局性的控制,此处就来聊聊使用它做全局日志的控制,并且支持动态日志的实现。

二.具体实现过程

1.定义切点注解

​ 我们可以使用注解的方式定义切点,也可以使用通配符进行配置,此处我们使用注解的方式进行配置。

@Target({
    
    ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface LogAnnotation {
    
    

    //模块名称
    ModuleNameEnum moduleName() default ModuleNameEnum.UNKNOWN;

    //操作对象
    String operaName() default "";

    //操作类型
    OperaTypeEnum operaType() default OperaTypeEnum.UNKNOWN;

}

@Target:定义了此注解的作用范围,ElementType.METHOD:作用在方法上;

@Retention:定义此注解的生命周期,RetentionPolicy.RUNTIME:注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;

@Documented:定义文档。

上面我们定义了一个注解LogAnnotation,此注解有三个参数,分别用于使用注解时传递对应的日志参数值。

2.定义枚举值

​ 上面的注解中我们有使用枚举值作为参数,使用枚举值的好处就是,以后对枚举值的修改,只用修改枚举类就行,不用逐个去改引用此值的地方。

@AllArgsConstructor
@ToString
public enum ModuleNameEnum implements Value<String> {
    
    
    ManageUser("人员管理"),
    SysUser("用户管理"),
    FeedBack("信息管理"),
    Publish("发布管理"),
    UNKNOWN("未定义");

    @JsonValue
    @Getter
    private final String value;

}
@AllArgsConstructor
@ToString
public enum OperaTypeEnum implements Value<String> {
    
    

    Insert("新增"),
    Update("更新"),
    Delete("删除"),
    UNKNOWN("未定义");

    @JsonValue
    @Getter
    private final String value;
}

public interface Value<T> {
    
    
    T getValue();
}

@AllArgsConstructor:使用注解生成类的构造函数;

@ToString:使用注解生成类的tostring方法;

@JsonValue:对于枚举值的格式定义;

@Getter:对枚举值添加获取具体值的方法。

3.定义日志切面类

​ 定义一个日志切面类,对方法进行拦截,对操作进行日志保存。

//@Aspect:标识当前类为一个切面,在spring容器加载的时候,会扫描到它
//@Component:把普通pojo实例化到spring容器中,类似于xml配置时的<bean>
//@Slf4j:定义日志
@Aspect
@Component
@Slf4j
public class LogAspect {
    
    

    //注入日志service类
    @Autowired
    XXXLogService xxxLogService;


    //节点:使用LogAnnotation注解标识的方式都进行切入,也可以使用通配符配置具体要接入的方法名
    @Pointcut("@annotation(xxx.aop.LogAnnotation)")
    public void pointCut(){
    
    

    }

    //环绕通知,
    @Around("pointCut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
    
    
        //方法执行之后的结果值
        Object jsonResult = joinPoint.proceed();
        try {
    
    
            //获取请求结果值
            MethodSignature signature = (MethodSignature)joinPoint.getSignature();
            //获取切入点所在的方法
            Method method = signature.getMethod();
            //获取到注解
            LogAnnotation annotation = method.getAnnotation(LogAnnotation.class);
            //获取注解里面字段的值
            String moduleName = annotation.moduleName().getValue();
            String operaName = annotation.operaName();
            String operaType = annotation.operaType().getValue();
            //获取到方法调用时传递的参数名
            String[] parameterNames = signature.getParameterNames();
            //获取到方法调用时各个参数的值
            Object[] args = joinPoint.getArgs();
            //使用替代的方式,获取动态日志
            operaName = getOperaName(parameterNames,args,operaName);
            //创建日志实体类
            XxxLog xxxLog = new XxxLog();
            xxxLog.setModuleName(moduleName);
            xxxLog.setOperaName(operaName);
            xxxLog.setOperaType(operaType);
            //获取登录用户信息,可以根据自己业务系统的方式进行获取
            xxxLog.setCreateUserId(StpUtil.getLoginIdAsString());
            //调用保存日志的方法
            xxxLogService.insertOne(xxxLog);
        } catch (Exception e){
    
    
            e.printStackTrace();
        } catch (Throwable e) {
    
    
            e.printStackTrace();
        }
        //返回方法执行之后的结果值
        return jsonResult;
    }

    /**
     * @Description: 使用替代的方式实现动态日志
     * @Author: zhanglizeng
     * @Param: [parameterNames, args, operaName]
     * @Return: java.lang.String
     */
    private String getOperaName(String[] argNames, Object[] args, String operaName) {
    
    
        //先把所有的参数进行遍历,作为替代的key,参数值作为替代的value
        Map<Object, Object> map = new HashMap<Object, Object>();
        for(int i = 0;i < argNames.length;i++){
    
    
            //参数和参数值是一一对应的,可以这样进行设置值
            map.put(argNames[i],args[i]);
        }
        try {
    
    
            //此处我们使用了operaName的值包含参数key的字符方式,例operaName="添加用户{
    
    {name}}"
            //而name作为方法调用时传递的一个参数名,在argNames[]数组中可以取到它,
            //name的参数值在args[]数组中可以取到它,然后我们使用字符替代的方式即可把operaName的值变为operaName="添加用户张三"
            for (Map.Entry<Object, Object> entry : map.entrySet()) {
    
    
                Object k = entry.getKey();
                Object v = entry.getValue();
                operaName = operaName.replace("{
    
    {" + k + "}}", JSONObject.toJSONString(v));
            }
        }catch (Exception e){
    
    
            e.printStackTrace();
        }
        return operaName;
    }
}

@Aspect:标识当前类为一个切面,在spring容器加载的时候,会扫描到它;
@Component:把普通pojo实例化到spring容器中,类似于xml配置时的;
@Slf4j:定义日志;

@Pointcut:定义切点;

@annotation(xxx.aop.LogAnnotation):定义切点的入口是注解,并且指明注解的目录,此处也可以使用通配符的方式配置切点的入口,此处我们使用注解的方式;

@Around(“pointCut()”):环绕通知,环绕的方法是上面切点的方法;

4.方法中配置注解

我们只需要在需要进行日志记录的方法上,添加我们的注解即可进行日志的添加。

    @LogAnnotation(moduleName= ModuleNameEnum.ManageUser,operaName="添加用户{
    
    {userName}}",operaType=OperaTypeEnum.Insert)
    public XxxUser addUser(String currentUserId,String userName) {
    
    
        XxxUser xxxUser = new XxxUser();
        xxxUser.setUserName(userName);
        xxxUserDao.addUser(xxxUser);
        return xxxUser;
    }

@LogAnnotation:使用注解标识调用此方法时需要执行aop的切面;可以在此处设置注解中字段的值,我们使用{ {}}这样的特殊符号来作为替代字符的标识;注意{ {}}里面的值需要与参数名保持一致,这样在后续替代的时候才能匹配的上。例如此处传递的userName值为张三,则替代后的operaName值为:添加用户张三。
执行结果截图:
在这里插入图片描述

5.调用流程

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/ZHANGLIZENG/article/details/126414237