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

  1. 使用 Thread.currentThread().getStackTrace() 方法
  2. 使用异常对象的 getStackTrace() 方法
  3. 使用匿名内部类的 getClass().getEnclosingMethod() 方法
  4. 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 流
  • findFirst() 方法从 Stream 流中的获取第一个元素,也就是堆栈的顶部帧,顶部帧就代表当前正在执行的方法
  • map() 方法用于获取顶部帧 StackFrame 的当前方法名称

Stack-Walking API 的优点

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

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

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

推荐作者开源的 H5 商城项目 waynboot-mall,这是一套全部开源的微商城项目,包含三个项目:运营后台、H5 商城前台和服务端接口。实现了商城所需的首页展示、商品分类、商品详情、商品 sku、分词搜索、购物车、结算下单、支付宝/微信支付、收单评论以及完善的后台管理等一系列功能。 技术上基于最新得 Springboot3.0、jdk17,整合了 MySql、Redis、RabbitMQ、ElasticSearch 等常用中间件。分模块设计、简洁易维护,欢迎大家点个 star、关注我。

github 地址:https://github.com/wayn111/waynboot-mall

经典例子: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;
}
}
}

方法逻辑如下,

  1. StackTraceElement[] cda = le.getCallerData() 获取当前堆栈顶部帧
  2. 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;
}
...
}
  1. 如果当前堆栈为空,进行堆栈信息初始化。这里就可以看到堆栈信息初始化来自 CallerData.extract(new Throwable(), fqnOfLoggerClass,loggerContext.getMaxCallerDataDepth(), loggerContext.getFrameworkPackages()) 方法。
  2. 如果堆栈信息不为空,直接返回当前堆栈。这里是为了避免浪费,针对在一个方法中重复获取堆栈信息的情况。

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,并且还是线程安全的。

关注公众号【waynblog】每周分享技术干货、开源项目、实战经验、国外优质文章翻译等,您的关注将是我的更新动力!

Java 面试题之 Logback 打印日志是如何获取当前方法名称的?的更多相关文章

  1. 用SLF4j/Logback打印日志-3

    在 用SLF4j/Logback打印日志-1 和 用SLF4j/Logback打印日志-2 中分别介绍了Logback记录日志的基本原理并重点介绍了输出源配置.本篇介绍一些性能和技巧性的东西. 性能 ...

  2. 用SLF4j/Logback打印日志-1

    在 浅谈后端日志系统 中已经写了很多日志方面的零散的非技术的东西.本篇更像一份入门说明,讲解一下SLF4j/Logback.SLF4J是一套抽象的日志API接口,logback它是的底层实现,所以在这 ...

  3. Spring Boot(三):logback打印日志

    springboot对logback的支持是非常好的,不需要任何配置,只需要在resource下加logback.xml就可以实现功能直接贴代码: <?xml version="1.0 ...

  4. springboot中logback打印日志(转)

    springboot对logback的支持是非常好的,不需要任何配置,只需要在resource下加logback.xml就可以实现功能 直接贴代码: <?xml version="1. ...

  5. 用SLF4j/Logback打印日志-2

    本篇主要介绍logback的输出源配置,logback默认提供了很多输出源,但是用的最多的是这几种: OutputStreamAppender 日志输出到一个二进制流,可以通过 <encoder ...

  6. Java面试题之多线程打印

    概述 作为程序员经常在面试的时候遇到多线程的问题,我印象比较深刻的就是下面这道题:写两个线程,一个线程打印 1~52,另一个线程打印字母A-Z.打印顺序为12A34B56C……5152Z.看这个题目已 ...

  7. logback打印日志时添加上下文

    尝试上述特性, 配置如下: 效果:

  8. springboot中logback打印日志

    http://blog.csdn.net/fan510988896/article/details/54409790

  9. java面试题:当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?

    答:是值传递.Java编程语言只有值传递参数. 当一个对象实例作为一个参数被传递到方法中时,参数的值就是该对象的引用一个副本.指向同一个对象,对象的内容可以在被调用的方法中改变,但对象的引用(不是引用 ...

  10. RestAssured打印日志到文件中的方法

    参考https://stackoverflow.com/questions/14476112/how-to-get-rest-assured-log-into-something-printable- ...

随机推荐

  1. Blazor中CSS隔离无法用在子组件内部

    Blazor的CSS隔离是个很好的东西,如图,只需添加一个与Razor组件同名的CSS文件,这个文件中的CSS样式只会在同名的Razor组件中使用. 原理是通过在dom元素添加一个代表标识符的属性 在 ...

  2. Ubuntu20.04 下编译和运行 FreeSWITCH的问题汇总

    1. Ubuntu20.04 下编译和运行 FreeSWITCH的问题汇总 1.1. 环境 Ubuntu20.04.2 LTS (Linux 5.4.0-152-generic x86_64 GNU/ ...

  3. 表格JS实现在线Excel的附件上传与下载

    摘要:本文由葡萄城技术团队于博客园原创并首发.转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 前言 在本地使用Excel时,经常会有需要在Excel中添加一 ...

  4. MIT6.s081/6.828 lectrue5/6:System call entry/exit 以及 Lab4 心得

    这篇博客主要复习 lecture05:GDB calling conentions 和 lecture06:System call entry/exit 的内容,外加 Lab4:traps 的心得 前 ...

  5. 1、MyBatis简介

    1.1.MyBatis历史 MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁 移到了Google Code. ...

  6. 使用vscodep快速编写markdown

    写在前面 这是一篇基于 vscode 配置,用于书写 markdown 的文章 为了方便快速书写 markdown 真想使用一些便捷的快捷键去生成一些自己常用的格式或者是模版,于是自己基于自己的个人习 ...

  7. 【krpano】图文案例

    KRPano图文案例可以展示图片和文字,并支持图片放大浏览,以及文本拖动等操作. 具体截图如下: 下载地址:http://pan.baidu.com/s/1qXQnPes 感谢群内小伙伴 快乐分享 本 ...

  8. UM 百度富文本编辑器自定义图片上传路径

    UM 百度富文本编辑器自定义图片上传路径 因为公司要做图文编辑,选择了UM,但是直接存入Tomcat根目录下,不满足业务需求需要存入服务器上. 一.需要注意的是在um的JSP目录下已经存在了Uploa ...

  9. 【火坑】一切从TimeSpan说起

    小编在编写WPF程序时,需要做一个判断:定时使用Modbus协议使用Quartz.net 定时任务读取设备中的数据,同时也使用定时任务判断是否长时间获取不到数据的情况,如果程序中超过一分钟没有获取到数 ...

  10. Building-Mobile-Apps-with-Ionic-2中文翻译工作

    最近没啥工作量, 然后学完了这本书, 接着又茫然找不到该干啥, 所以想着何不翻译这个书呢. 这本书首先给我们普及了Ionic 2的基础知识, Ionic 2和Ionic 1有本质上的区别, Ionic ...