Java面试Logback打印日志如何获取当前方法名称题解

 更新时间:2023年11月12日 14:58:51   作者:wayn  
这篇文章主要为大家介绍了Java面试Logback打印日志如何获取当前方法名称题解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

在Java中有四种方法可以获取当前正在执行方法体的方法名称

分别是:

  • 使用 Thread.currentThread().getStackTrace() 方法
  • 使用异常对象的 getStackTrace() 方法
  • 使用匿名内部类的 getClass().getEnclosingMethod() 方法
  • Java 9 的 Stack-Walking API

本文将根据以上四种方法来给大家进行具体讲解,不过不知道大家有没有想过,获取当前执行方法体的方法名称有什么用嘞?

它可以用于日志记录、异常处理、测试框架等方面。例如我们可以在方法的开始和结束时打印出当前方法名和参数,以便追踪程序的执行流程和性能。在介绍完以上四种方法后,就会给大家揭晓面试题答案。

1.使用 Thread.currentThread().getStackTrace()方法

这种方法是通过获取当前线程的堆栈跟踪信息,然后从中提取出当前方法名的。具体的代码如下:

// 获取当前方法名
String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
// 打印当前方法名
System.out.println("当前方法名:" + methodName);

这种方法的优点是简单易用,不需要创建额外的对象。缺点是性能较低,因为 Thread.currentThread().getStackTrace() 方法获取堆栈跟踪信息需要遍历整个调用栈,而且需要保证线程安全性。

2.使用异常对象的 getStackTrace()方法

这种方法是通过创建一个新的异常对象,然后从其堆栈跟踪信息中提取出当前方法名和参数的。具体的代码如下:

// 获取当前方法名
String methodName = new Exception().getStackTrace()[0].getMethodName();
// 打印当前方法名
System.out.println("当前方法名:" + methodName);

这种方法的优点是不需要获取堆栈跟踪信息,而且不会创建异常对象,因此性能和可读性都较好。缺点是需要创建额外的对象,而且代码较为复杂,不太直观。

3.匿名内部类的 getClass().getEnclosingMethod()方法

这种方法是通过创建一个匿名内部类的对象,然后从其类对象中获取当前方法的方法对象,再从方法对象中获取当前方法名和参数的。具体的代码如下:

// 获取当前方法名
String methodName = new Object(){}.getClass().getEnclosingMethod().getName();
// 打印当前方法名
System.out.println("当前方法名:" + methodName);

这种方法的优点是不需要获取堆栈跟踪信息,而且不会创建异常对象,因此性能和可读性都较好。缺点是需要创建额外的对象,而且代码较为复杂,不太直观。

4.Java 9 的 Stack-Walking API

Java 9 引入了 Stack-Walking API,以惰性且高效的方式遍历 JVM 堆栈帧。可以使用这个 API 找到当前正在执行的方法,具体的代码如下:

StackWalker walker = StackWalker.getInstance();
Optional<String> optional = walker.walk(frames -> frames
        .findFirst()
        .map(StackWalker.StackFrame::getMethodName));
System.out.println("当前方法名:" + optional.get());

首先,我们使用 StackWalker.getInstance() 工厂方法获取 StackWalker 实例。然后我们使用 walk() 方法从上到下遍历栈帧:

  • walk() 方法可以将堆栈帧转化为 Stream<StackFrame> 流
  • findFirst() 方法从 Stream 流中的获取第一个元素,也就是堆栈的顶部帧,顶部帧就代表当前正在执行的方法
  • map() 方法用于获取顶部帧 StackFrame 的当前方法名称

Stack-Walking API 的优点

与以上方法相比,Stack-Walking API 有很多优点:

  • 线程安全
  • 无需创建匿名内部类实例 - new Object().getClass(){}
  • 无需创建异常 - new Throwable()
  • 无需急切地捕获整个堆栈跟踪,这可能成本很高 - Thread.currentThread()

StackWalker 是以一种懒惰的方式逐一遍历堆栈。在需要获取当前方法名称时,我们可以只获取顶部帧,而不需要捕获整个堆栈跟踪。

经典例子:Logback

Logback 是一个流行的 Java 日志框架,它是 Log4j 的继承者,由 Log4j 的创始人设计。Logback 有以下特点:

  • 高性能:Logback 比其他日志框架更快,更节省空间,有时甚至大得多。
  • 灵活配置:Logback 支持 XML 和 Groovy 两种配置方式,可以实现动态修改配置,无需重启应用。
  • 丰富功能:Logback 提供了多种输出目标,如控制台、文件、数据库、邮件等,还支持滚动策略、过滤器、异步日志等高级功能。
  • 与 SLF4J 集成:Logback 是 SLF4J 的原生实现,可以与其他基于 SLF4J 的日志框架无缝切换。

不知道大家有没有想过,我们在使用 Logback 日志框架中打印日志时,是如何获取当前执行方法体的方法名称的嘞?在 Spring 项目中,我们一般是通过 Logback 的 xml 文件 parttern 属性来配置日志格式的。xml 配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <springProperty scope="context" name="appName" source="spring.application.name" defaultValue="dev"/>
    <property name="logPath" value="/home/logs/${appName}"/>
    <property name="pattern"
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{request_id}] [%thread] [%-5level] %logger{36}:%L %M - %msg%n"/>
    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- encoder 默认配置为PatternLayoutEncoder -->
        <encoder>
            <pattern>${pattern}</pattern>
        </encoder>
    </appender>
    <!-- 记录日志到文件 -->
    <appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${logPath}/info.log</file>
        <encoder>
            <pattern>${pattern}</pattern>
        </encoder>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${logPath}/run.%d{yyyy-MM-dd}.log</fileNamePattern>
        </rollingPolicy>
    </appender>
    ...
</configuration>

可以看到我们配置的日志输出格式是 %d{yyyy-MM-dd HH:mm:ss.SSS} [%X{request_id}] [%thread] [%-5level] %logger{36}:%L %M - %msg%n,Logback 在打印日志时,会解析这个日志输出格式,最后将 %M 占位符替换为当前方法名称。

解析日志格式的源码就在 FormattingConverter 类的 write() 方法中,write() 方法中会执行 convert() 方法,这个方法就是执行占位符替换的。源码截图如下,

如上图根据类名我们可以看到红线框起来的 MethodOfCallerConverter 类就是用来执行 %M 占位符替换逻辑的,代码如下,

public class MethodOfCallerConverter extends ClassicConverter {
    public String convert(ILoggingEvent le) {
        StackTraceElement[] cda = le.getCallerData();
        if (cda != null && cda.length > 0) {
            // 返回当前方法名称
            return cda[0].getMethodName();
        } else {
            return CoreConstants.NA;
        }
    }
}

方法逻辑如下,

  • StackTraceElement[] cda = le.getCallerData() 获取当前堆栈顶部帧
  • cda[0].getMethodName() 根据顶部帧获取当前方法名称。

如上,我们只需要看下 le.getCallerData() 方法的堆栈是从哪里获取来的,就能知道本题的答案了。

进入 LoggingEvent 源码类中,我们可以发现堆栈获取逻辑,源码如下,

public class LoggingEvent implements ILoggingEvent {
    public StackTraceElement[] getCallerData() {
        if (callerDataArray == null) {
            // 堆栈初始化
            callerDataArray = CallerData.extract(new Throwable(), fqnOfLoggerClass,
                    loggerContext.getMaxCallerDataDepth(), loggerContext.getFrameworkPackages());
        }
        return callerDataArray;
    }
    ...
}
  • 如果当前堆栈为空,进行堆栈信息初始化。这里就可以看到堆栈信息初始化来自 CallerData.extract(new Throwable(), fqnOfLoggerClass,loggerContext.getMaxCallerDataDepth(), loggerContext.getFrameworkPackages()) 方法。
  • 如果堆栈信息不为空,直接返回当前堆栈。这里是为了避免浪费,针对在一个方法中重复获取堆栈信息的情况。

Ok,到这里离胜利就只差一步了。进一步查看 CallerData.extract(new Throwable(), fqnOfLoggerClass,loggerContext.getMaxCallerDataDepth(), loggerContext.getFrameworkPackages()) 方法,源码如下,

public class CallerData {
    public static StackTraceElement[] extract(Throwable t, String fqnOfInvokingClass, final int maxDepth,
            List<String> frameworkPackageList) {
        if (t == null) {
            return null;
        }

        StackTraceElement[] steArray = t.getStackTrace();
        StackTraceElement[] callerDataArray;
        ...
        callerDataArray = new StackTraceElement[desiredDepth];
        for (int i = 0; i < desiredDepth; i++) {
            callerDataArray[i] = steArray[found + i];
        }
        return callerDataArray;
    }
    ...
}

为了突出源码逻辑的重点,这里我删去了一部分代码,是为了让大家更好的看清楚 Logback 中堆栈信息的初始化,其实用的就是异常对象的 getStackTrace() 方法。也就是上面源码中 StackTraceElement[] steArray = t.getStackTrace() 方法所体现的。

那么到这里我就可以下一个结论了, Logback 日志框架中打印日志时,就是使用异常对象的 getStackTrace() 方法来获取当前执行方法的方法名称的。

总结

本文有介绍四种方法获取当前执行方法名称,一般情况下大家使用异常对象的 getStackTrace() 方法以及匿名内部类的 getClass().getEnclosingMethod() 方法都是可以的,它们的性能都 OK,代码书写复杂程度都大差不差。在 Java 9 以后推荐使用 Stack-Walking API,它的功能更为强大,与程序里的堆栈语意也跟为契合,性能 OK,并且还是线程安全的。

以上就是Java面试Logback打印日志如何获取当前方法名称题解的详细内容,更多关于Java Logback获取打印日志方法名的资料请关注脚本之家其它相关文章!

相关文章

  • JAVA程序员不得不留意的编码规范

    JAVA程序员不得不留意的编码规范

    这篇文章主要介绍了JAVA程序员不得不留意的编码规范,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-07-07
  • 聊聊@RequestMapping和@GetMapping @PostMapping的区别

    聊聊@RequestMapping和@GetMapping @PostMapping的区别

    这篇文章主要介绍了@RequestMapping和@GetMapping及@PostMapping的区别,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • Java中与数字相关的常用类的用法详解

    Java中与数字相关的常用类的用法详解

    在我们的代码中,经常会遇到一些数字&数学问题、随机数问题、日期问题和系统设置问题等,为了解决这些问题,Java给我们提供了多个处理相关问题的类,比如Number类、Math类、Random类等等,本篇文章我们先从Number数字类和Math数学类学起
    2023-05-05
  • Spring Boot 统一数据返回格式的解决方案

    Spring Boot 统一数据返回格式的解决方案

    统⼀的数据返回格式使⽤ @ControllerAdvice 和 ResponseBodyAdvice 的⽅式实现,下面给大家分享Spring Boot 统一数据返回格式的解决方案,感兴趣的朋友一起看看吧
    2024-03-03
  • Mybatis-plus与Mybatis依赖冲突问题解决方法

    Mybatis-plus与Mybatis依赖冲突问题解决方法

    ,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧这篇文章主要介绍了Mybatis-plus与Mybatis依赖冲突问题解决方法
    2021-04-04
  • 简单总结Java的反射机制的运用

    简单总结Java的反射机制的运用

    这篇文章主要介绍了Java的反射机制的运用,对一些常用的类作了一些说明,需要的朋友可以参考下
    2015-11-11
  • SSH框架网上商城项目第18战之过滤器实现购物登录功能的判断

    SSH框架网上商城项目第18战之过滤器实现购物登录功能的判断

    这篇文章主要为大家详细介绍了SSH框架网上商城项目第18战:过滤器实现购物登录功能的判断,感兴趣的小伙伴们可以参考一下
    2016-06-06
  • Java实现整合文件上传到FastDFS的方法详细

    Java实现整合文件上传到FastDFS的方法详细

    FastDFS是一个开源的轻量级分布式文件系统,对文件进行管理,功能包括:文件存储、文件同步、文件上传、文件下载等,解决了大容量存储和负载均衡的问题。本文将提供Java将文件上传至FastDFS的示例代码,需要的参考一下
    2022-02-02
  • Spring Cloud 网关服务 zuul  动态路由的实现方法

    Spring Cloud 网关服务 zuul 动态路由的实现方法

    网关服务是流量的唯一入口。不能随便停服务。所以动态路由就显得尤为必要。这篇文章主要介绍了Spring Cloud 网关服务 zuul 三 动态路由的相关知识,需要的朋友可以参考下
    2019-10-10
  • hibernate关于session的关闭实例解析

    hibernate关于session的关闭实例解析

    这篇文章主要介绍了hibernate关于session的关闭实例解析,分享了相关代码示例,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
    2018-02-02

最新评论