欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

基于AOP的日志输出自定义注解

程序员文章站 2024-03-26 11:23:53
...

前言

在企业开发中,日志输出是一种习惯,也是开发中强制要求的,这样可以快速定位问题,当然可以收集日志进行数据分析,每个公司都有自己的风格,就我们公司的日志输出做一个简单的展示

  1. 公司日志风格
@Override
  public classA<classB>methodName(String logId,ByxRepaymentScheduleModel model) {
      LoggerUtils.infoForStart(Class clazz, String businessName,String businessMethod, String businessId, Object inputParamObj)
try{
      ..............................(省略)

      LoggerUtils.infoForEnd(Class clazz, String businessName,String businessMethod, String businessId, Object outputParamObj);

}catch{
LoggerUtils.error(Class clazz, String businessName, String businessMethod, String businessId, Exception e)
}
      return ***;
  }

便于日志分析,bug排查这是我们公司最基本的日志输出格式,开始->结束-> 异常,一目了然

  1. 问题

以上三行每个方法都要写一遍,有的同学想第一时间想到CV也就几秒的事,说的也是,CV类名、方法名、注解、入参、返回结果,现在想想都有点恶心。

  1. 解决

为了解决上面问题,观察日志输出规律,你可以很轻松的想到Aop。。。。。是的没错就是Aop,完美解决上述问题

  1. 上代码
    (1).配置类
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * @author: ralap
 * @date: created at 2018/5/4 15:26
 */
@Configuration
@ComponentScan("com.itfan.kernel.service")
@EnableAspectJAutoProxy
public class LogConfig {

}

添加配置信息,扫描指定路径,开启Aop的代理自动配置。

(2).自定义注解

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

/**
 * @author: ralap
 * @date: created at 2018/5/4 15:28
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ItfanLogMessage {

    String value() default "";
}

自定义注解,添加属性value

(4).定义切面

import java.lang.reflect.Method;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * @author: ralap
 * @date: created at 2018/5/4 15:31
 */
@Aspect
@Component
public class LogAspact {

    private static final Logger logger = LoggerFactory.getLogger(LogAspact.class);

    @Pointcut("@annotation(com.itfan.kernel.config.log.ItfanLogMessage)")
    public void annotationPointCut() {

    }

    @Around("annotationPointCut()")
    public Object around(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String className = signature.getDeclaringTypeName();
        String methodName = signature.getName();
        Method method = signature.getMethod();
        Object[] args = joinPoint.getArgs();
        String logNo = null;
        Object proceed = null;
        ItfanLogMessage action = method.getAnnotation(ItfanLogMessage.class);
        String agrsStr = "";
        for (int i = 0; i < args.length - 1; i++) {
            if (i < args.length - 2) {
                agrsStr = agrsStr + args[i] + ",";
            } else {
                agrsStr = agrsStr + args[i];
            }

        }
        logNo = (String) args[args.length - 1];
        try {
            logger.info("【开始】--->类名:【" + className + "】, 方法名【"
                    + methodName + "】,方法描述【" + action.value() + "】,入参【" + agrsStr + "】,日志流水号【"
                    + logNo + "】");
            Long startTime = System.currentTimeMillis();
            proceed = joinPoint.proceed();
            logger.info("【结束】--->类名:【" + className + "】, 方法名【"
                    + methodName + "】,方法描述【" + action.value() + "】,返回参数【" + proceed.toString()
                    + "】,日志流水号【" + logNo + "】,耗时【"+(System.currentTimeMillis()-startTime) +"毫秒】");
        } catch (Throwable throwable) {
//            throwable.printStackTrace();
            logger.error("【异常】--->类名:【" + className + "】, 方法名【"
                    + methodName + "】,方法描述【" + action.value() + "】,日志流水号【"
                    + logNo + "】,异常信息【" + throwable.getMessage() + "】");
        }

        return proceed;
    }
}

定义切面,配置切点为自定义的注解,根据实际需求,选择Around环绕通知,要求入参的最后一个参数为日志流水号,具体实现细节请看上面代码。

(5)用法

    @Override
    @ItfanLogMessage("获取轮播视频")
    public List<Video> getCarousel(String logNo) {
        List<Video> viewList = redisManager
         .getViewList(RedisKey.VIDEO_PREFIX_HOME_CAROUSEL_KEY, VideTag.LETV_TAG);
        return viewList;
    }

总结
可以对比二者日志输出,很显然,使用AOP大大节省了CV时间,同时也可以规范代码开发,效率更高。