一个注解解决接口耗时日志的打印

本文介绍了如何使用Java的AOP(面向切面编程)和SpEL表达式创建一个自定义注解,用于在方法执行前后计算耗时并生成可定制的日志内容,示例展示了如何在注解中提取参数和表达式进行日志格式化。
摘要由CSDN通过智能技术生成

在日常开发中,常常需要统计方法的耗时情况,一般的写法是在进入方法之前,记录一下当前时间戳,在方法最后再用当前时间戳减去进入时候的时间戳就是耗时情况,方法很简单,但不够优雅。
接下来我们用一个注解+AOP的方式来实现这个需求

首先,自定义一个注解 @StopWatch

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

/**
 * 方法耗时日志
 *
 * @author 敖癸
 * @formatter:on
 * @since 2023/11/29
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface StopWatch {

    /** 日志内容模板, ex: "topic: {}" */
    String value() default "";

    /** 模板参数,支持SpEL表达式, ex: {"#serialNo", "第二个参数"} */
    String[] args() default {};
}

然后再实现一个切面


import cn.hutool.core.lang.Opt;
import com.dmjy.pub.common.StopWatch;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.MDC;

/**
 * 耗时日志注解切面
 *
 * @author 敖癸
 * @formatter:on
 * @since 2023/11/29
 */
@Slf4j
@Aspect
@RequiredArgsConstructor
public class StopWatchAspect {

    private static final SpelExpressionParser PARSER = new SpelExpressionParser();
    
    @Around("@annotation(stopWatch)")
    public Object around(ProceedingJoinPoint joinPoint, StopWatch stopWatch) throws Throwable {
        long startTime = System.currentTimeMillis();
        try {
            return joinPoint.proceed();
        } finally {
            long totalMs = System.currentTimeMillis() - startTime;
            MDC.put("costTime", String.valueOf(totalMs));
            String content = buildTemplate(joinPoint, stopWatch);
            content = Opt.ofBlankAble(content).orElseGet(() -> joinPoint.getSignature().toShortString());
            if (log.isDebugEnabled()) {
                log.debug("{}, 执行耗时 {} ms", content, totalMs);
            } else {
                log.info("{}, 执行耗时 {} ms", content, totalMs);
            }
        }
    }


    /**
     * SpEL模板解析
     *
     * @param context     切点的上下文对象
     * @param template    字符串模板
     * @param expressions SpEL参数
     * @return java.lang.String
     * @author 敖癸
     * @since 2024/3/5 - 17:03
     */
    private static String buildTemplate(ProceedingJoinPoint joinPoint, StopWatch stopWatch) {
        String template = stopWatch.value();
        String[] expressions = stopWatch.args();
        if (StrUtil.isNotBlank(template) && expressions != null) {
            EvaluationContext context = buildEvaluationContext(joinPoint)
            Object[] args = Arrays.stream(expressions).map(expression -> {
                if (StrUtil.startWith(expression, "#")) {
                    return PARSER.parseExpression(expression).getValue(context);
                }
                return expression;
            }).toArray();
            template = StrUtil.format(template, args);
        }
        return template;
    }
    
    /**
     * 解析切点的上下文对象
     *
     * @param joinPoint aop切点
     * @return org.springframework.expression.EvaluationContext
     * @author 敖癸
     * @since 2024/3/5 - 17:05
     */
    private static EvaluationContext buildEvaluationContext(ProceedingJoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String[] argNames = signature.getParameterNames();
        EvaluationContext context = new StandardEvaluationContext();

        Object[] methodArgs = joinPoint.getArgs();
        for (int i = 0; i < methodArgs.length; i++) {
            context.setVariable(argNames[i], methodArgs[i]);
        }
        return context;
    }
}

使用示例:

    @StopWatch(value = "whatsappUser回调: phone: {}, {}", args = {"#dto.vcPhone", "#dto.isMatch()?\"匹配成功\":\"匹配失败\""})
    public void consumerFunc(WSUserInfoDTO dto) {
        ThreadUtil.sleep(345);
    }

在这里插入图片描述
这个注解不光可以取出参数中对象中的属性,方法,还可以解析表达式,来自定义日志的内容

  • 14
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你可以使用 @Slf4j 注解和 AOP 统一处理打印日志,具体实现可参考以下代码: 1. 引入依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </dependency> ``` 2. 在应用主类上添加 @EnableAspectJAutoProxy 注解启用 AOP: ```java @SpringBootApplication @EnableAspectJAutoProxy(proxyTargetClass = true) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` 3. 定义切面类: ```java @Aspect @Component @Slf4j public class LogAspect { @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)" + "||@annotation(org.springframework.web.bind.annotation.GetMapping)" + "||@annotation(org.springframework.web.bind.annotation.PostMapping)" + "||@annotation(org.springframework.web.bind.annotation.PutMapping)" + "||@annotation(org.springframework.web.bind.annotation.DeleteMapping)") public void webLog() { } @Before("webLog()") public void doBefore(JoinPoint joinPoint) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // 记录请求内容 log.info("URL : " + request.getRequestURL().toString()); log.info("HTTP_METHOD : " + request.getMethod()); log.info("IP : " + request.getRemoteAddr()); // 记录调用方法 log.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); // 记录请求参数 log.info("ARGS : " + Arrays.toString(joinPoint.getArgs())); } @AfterReturning(returning = "ret", pointcut = "webLog()") public void doAfterReturning(Object ret) { // 记录响应内容 log.info("RESPONSE : " + ret); } } ``` 4. 在需要打印日志接口方法上添加 @RequestMapping 等注解即可。 压缩文件的操作请参考以下代码: ```java public static void zip(Path sourcePath, Path zipPath) throws IOException { try (ZipOutputStream zos = new ZipOutputStream(Files.newOutputStream(zipPath)); Stream<Path> paths = Files.walk(sourcePath)) { paths.filter(p -> !Files.isDirectory(p)) .forEach(p -> { ZipEntry entry = new ZipEntry(sourcePath.relativize(p).toString()); try { zos.putNextEntry(entry); zos.write(Files.readAllBytes(p)); zos.closeEntry(); } catch (IOException e) { e.printStackTrace(); } }); } } ``` 对于中文加密的问题,我不是很确定您要表达的意思,请再提供更详细的问题描述。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值