SpringAOP日志管理的两种使用方法

关于SpringAOP日志管理的使用方法

在需要对业务日志进行特殊规则统一管理的时候,Slf4j提供的全局日志配置就不那么好使用了,这时候我们可以使用Spring的AOP特性来截断各个方法或接口,将其返回值或处理过程做统一管理,再落到日志中。

 

首先我们定义一个实体用来落日志,这个实体最后打印出来我希望字段是固定顺序的,字段为空则不打印,要巧妙的利用阿里的fastJson。

import com.alibaba.fastjson.annotation.JSONField;
import com.fasterxml.jackson.annotation.JsonInclude;
// 这个DateUtil是自己封装的,用来获取当前时间
import xxx.utils.DateUtil;
import lombok.Data;

import java.util.HashMap;
import java.util.Map;

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class LogData {

    /**
     * 日志主题
     */
    @JSONField(ordinal = 1)
    private String topic;

    /**
     * 类型
     */
    @JSONField(ordinal = 2)
    private String type = "default";


    /**
     * 启动时间戳
     */
    @JSONField(ordinal = 3)
    private String time;

    /**
     * 耗时
     */
    @JSONField(ordinal = 4)
    private Long cost;

    /**
     * 长度
     */
    @JSONField(ordinal = 5)
    private Integer size = 0;

    @JSONField(ordinal = 6)
    private Map<String, Object> data = new HashMap<>();

    /**
     * 异常
     */
    @JSONField(ordinal = 7)
    private String error;

    /**
     * 备注消息
     */
    @JSONField(ordinal = 8)
    private String msg;


    public LogData(String topic) {
        this.topic = topic;
        this.time = DateUtil.get24DateTime();
    }

    public LogData(String topic, String type) {
        this.topic = topic;
        this.type = type;
        this.time = DateUtil.get24DateTime();
    }

}

由于我们更倾向于使用注解来用切面,所以我们要创建一个Annotation,本注解对应级别是方法级的

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 打印查询日志
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PrintLog {
 
}

接下来是关键的日志切面

/**
 * 定义日志切面
 *
 * @Lazy 注解:容器一般都会在启动的时候实例化所有单实例 bean,如果我们想要 Spring 在启动的时候延迟加载 bean,需要用到这个注解
 * value为true、false 默认为true,即延迟加载,@Lazy(false)表示对象会在初始化的时候创建
 * @Modified By:
 */
@Aspect
@Component
@Lazy(false)
@Slf4j(topic = "traceLogger")
public class LoggerAspect {

    /**
     * 定义切入点:对要拦截的方法进行定义与限制,如包、类
     * <p>
     * 1、execution(public * *(..)) 任意的公共方法
     * 2、execution(* set*(..)) 以set开头的所有的方法
     * 3、execution(* com.poizon.annotation.LoggerApply.*(..))com.test.LoggerApply这个类里的所有的方法
     * 4、execution(* com.poizon.annotation.*.*(..))com.test.annotation包下的所有的类的所有的方法
     * 5、execution(* com.poizon.annotation..*.*(..))com.test.annotation包及子包下所有的类的所有的方法
     * 6、execution(* com.poizon.annotation..*.*(String,?,Long)) com.test.annotation包及子包下所有的类的有三个参数,第一个参数为String类型,第二个参数为任意类型,第三个参数为Long类型的方法
     * 7、execution(@annotation(com.test.annotation.poizon))
     */
    @Pointcut("execution(* com.test.services..*.* (..)) &&  @annotation(com.test.annotation.PrintLog)")
    private void cutMethod() {

    }


    /**
     * 后置通知:在目标方法执行后调用,若目标方法出现异常,则不执行

    @AfterReturning("cutMethod()")
    public void afterReturning(ProceedingJoinPoint pjp) {
        try {
            Object proceed = pjp.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }


    }*/

    @AfterReturning(returning = "rvt", pointcut = "execution(* com.test.services..*.*(..))")
    public void AfterExec(JoinPoint joinPoint, Object rvt) {
        // 这里是选择在方法执行完成后,对返回的结果进行拦截,如果返回的结果是LogData类型的,就对实体进行打印
        if (rvt == null) {
            return;
        }
        if (rvt instanceof LogData) {
            log.info(JsonUtil.writeValueAsString(rvt));
        }
    }

}

这样只要我们业务返回的数据都按照需求存进LogData这样的实体中即可。

 

但这样是否还是过于复杂了?我们通常一个方法可能本身就要返回一个实体,而现在要求返回一个指定实体给切面用于日志,很大程度影响到了业务,而且改造成本可能会比较高,那么用另一种切面方式

    @Around("@annotation(com.test.annotation.PrintLog)")
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        // 1.方法执行前的处理,相当于前置通知
        // 获取方法签名
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        // 获取方法
        Method method = methodSignature.getMethod();
        // 获取方法上面的注解
        PrintLog logAnno = method.getAnnotation(PrintLog.class);

        LogData logData = new LogData(method.getName());
        long startTime = System.currentTimeMillis();
        Object result = null;
        try{
            //让代理方法执行
            result = pjp.proceed();
            if(result instanceof Collection){
                logData.setSize(((Collection) result).size());
            }else if (result instanceof Map){
                logData.setData((HashMap<String,Object>)result);
            }
        }catch (Exception e){
            logData.setError(e.getMessage());
        }finally {
            logData.setCost(System.currentTimeMillis()-startTime);
        }
        return result;
    }

这样我们通过判断原有方法返回的内容,就能没有侵入性的切入各业务进行日志管理了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值