热爱技术,追求卓越
不断求索,精益求精

springboot项目使用自定义注解和aop记录类名方法名参数耗时信息

在前面的一篇文章《使用ThreadLocal和AOP做线程缓存提高性能,缩短API网关响应时间》中介绍了使用自定义注解和spring aop实现本地线程缓存。今天介绍一下springboot项目使用自定义注解和aop记录类名方法名参数耗时信息,实现日志打印。

针对方法做日志打印,主要是一些对耗时敏感的操作,如数据库查询,dubbo方法实现等,基于自定义注解和aop实现方法日志打印,一方面把日志和核心业务逻辑分开,另一方面代码也显得更加优雅可读,更重要的是,我们可以针对这些日志做一些性能监控,便于系统的重构优化。

自定义一个注解PrintLog,作用在方法上,只要在方法上使用了该注解的都会进行日志打印。可以选择需要打印到的logger,便于不同的使用场景适用不同的日志文件等,同时还可以选择是否打印参数,有时候参数可能很庞大,我们可以选择不打印参数,当然默认是需要打印的,便于我们追踪。注解类如下:

package cn.lovecto.promotion.annotation;

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

/**
 * 打印日志注解
 *
 */
@Retention(value = RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface PrintLog {

    /**
     * 选择打印日志的logger,不指定则默认是拦截器的logger
     * @return
     */
    String value() default "";

    /**
     * 是否打印参数,默认打印参数
     * @return
     */
    boolean printParams() default true;
}

有了注解类,我们来实现这个注解的拦截器PrintLogAnnotationInterceptor,这里面负责处理核心的日志打印逻辑,主要处理获取类名、方法名、参数列表、耗时信息等。PrintLogAnnotationInterceptor类如下:

package cn.lovecto.promotion.interceptor;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import cn.lovecto.promotion.annotation.PrintLog;

/**
 * 打印日志注解拦截器
 * 
 *
 */
@Aspect
@Component
public class PrintLogAnnotationInterceptor {

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

    /**
     * 针对方法执行打印方法及参数和耗时
     * 
     * @param joinPoint
     * @param printlog
     * @return
     * @throws Throwable
     */
    @Around(value = "@annotation(printlog)")
    public Object printLog(ProceedingJoinPoint joinPoint, PrintLog printlog)
            throws Throwable {
        long start = System.currentTimeMillis();
        Object result = null;
        try {
            result = joinPoint.proceed();
        } catch (Throwable e) {
            throw e;
        } finally {
            long end = System.currentTimeMillis();
            print(start, end, joinPoint, printlog);
        }
        return result;
    }

    /**
     * 打印逻辑
     * 
     * @param start
     * @param end
     * @param joinPoint
     * @param printlog
     */
    private void print(long start, long end, ProceedingJoinPoint joinPoint,
            PrintLog printlog) {
        try {
            Logger curentLogger = logger;
            if (!StringUtils.isEmpty(printlog.value())) {
                curentLogger = LoggerFactory.getLogger(printlog.value());
            }
            String msg = getTypeAndMethodAndParams(joinPoint,
                    printlog.printParams());
            long cost = end - start;
            if (cost > 100) {// 大于100毫秒
                curentLogger.warn("{} cost {} ms", msg, cost);
            } else {
                curentLogger.info("{} cost {} ms", msg, cost);
            }
        } catch (Exception e) {
            logger.error("print exception", e);
        }
    }

    /**
     * 获取执行方法的类名方面以及参数详情
     * 
     * @param joinPoint
     * @param printParams
     *            是否打印参数
     * @return
     */
    private String getTypeAndMethodAndParams(ProceedingJoinPoint joinPoint,
            Boolean printParams) {
        StringBuilder strBuilder = new StringBuilder();
        strBuilder.append(joinPoint.getTarget().getClass().getTypeName())
                .append(".").append(joinPoint.getSignature().getName())
                .append("(");
        if (printParams && joinPoint.getArgs().length > 0) {
            for (int i = 0; i < joinPoint.getArgs().length; i++) {
                strBuilder.append(joinPoint.getArgs()[i]);
                if (i != (joinPoint.getArgs().length - 1)) {
                    strBuilder.append(",");
                } else {
                    strBuilder.append(")");
                }
            }
        } else {
            strBuilder.append(")");
        }
        return strBuilder.toString();
    }
}

打印日志在finally代码块中,这样做主要是一种编程思想,永远不要相信自己的代码是完美的,不管目标方法是否执行,我们都会记录到日志;同时finally中的print方法里面也要使用try catch,避免因为这个日志功能运行时出错而影响正常业务。getTypeAndMethodAndParams这个方法主要是获取目标方法的类名、方法名、参数列表等,根据是否需要打印参数来决定是否遍历参数列表进行打印。此处简单使用StringBuilder,不管参数是否是普通来下还是对象,我们都建议定义的类实现toString方法(针对自己写的类实现toString方法是种良好的编程习惯),这样日志会更详尽。

接下来是使用的地方,使用将是非常的简单:

package cn.lovecto.promotion.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import cn.lovecto.promotion.annotation.PrintLog;
import cn.lovecto.promotion.dao.mapper.PromotionMapper;
import cn.lovecto.promotion.model.Promotion;

/**
 * 促销查询
 *
 */
@Service
public class PromotionDao {

    @Autowired
    private PromotionMapper promotionMapper;

    @PrintLog(value = "db")
    public int selectCount(Promotion promotion){
        return promotionMapper.selectCount(promotion);
    }

}

只需要在需要日志记录的方法上加上注解就可以啦!!!

赞(4)
未经允许不得转载:LoveCTO » springboot项目使用自定义注解和aop记录类名方法名参数耗时信息

热爱技术 追求卓越 精益求精