得到当前堆栈信息的两种方式(Thread和Throwable)的纠结

今天进行slf4j中logger的同步封装,主要目的是为了以后方便更换日志实现系统。

遇到的问题:使用Thread.currentThread().getStackTrace()[1].getClassName()得到的是当前类而不是调用类,见下面代码:

private org.slf4j.Logger logger = null;

	/**
	 * construction method
	 */
	public Logger(){
		// get the current class logger
		logger = LoggerFactory.getLogger(Thread.currentThread().getStackTrace()[1].getClassName());
	}

所以打印日志的时候并没有得到日志真正所属类的信息。

然后,我进行了一组测试:

测试A:

public class ThreadTest {

	public static void TestString(){
		StackTraceElement[] arr = new Exception().getStackTrace();
		for(int i=0;i<=arr.length-1;i++){
			System.out.println(arr[i].getClassName()+";"+arr[i].getMethodName()+";"+arr[i].getFileName());
		}
	}
}
public class App
{
    public static void main( String[] args )
    {
        ThreadTest.TestString();
    }
}

结果是:

test.ThreadTest;TestString;ThreadTest.java(当前方法和类)

test.App;main;App.java(调用该方法的方法和类)

测试B:

public class ThreadTest {

	public static void TestString(){
		StackTraceElement[] arr = Thread.currentThread().getStackTrace();
		for(int i=0;i<=arr.length-1;i++){
			System.out.println(arr[i].getClassName()+";"+arr[i].getMethodName()+";"+arr[i].getFileName());
		}
	}
}

App类同上,得到的结果是:

java.lang.Thread;getStackTrace;Thread.java(Thread的信息)

test.ThreadTest;TestString;ThreadTest.java(当前方法和类)

test.App;main;App.java(调用该方法的方法和类)

啥米情况???难道就是因为Thread的getStackTrace比Throwable(Exception的父类)的getStackTrace多打了一段Thread类的信息???

由于不确定是不是真的是这样子的,故去查看了下jdk的源码(纯属装X,不抱幻想)。

首先是Throwable的getStackTrace方法,代码如下:

 public StackTraceElement[] getStackTrace() {
        return getOurStackTrace().clone();
    }

    private synchronized StackTraceElement[] getOurStackTrace() {
        // Initialize stack trace field with information from
        // backtrace if this is the first call to this method
        if (stackTrace == UNASSIGNED_STACK ||
            (stackTrace == null && backtrace != null) /* Out of protocol state */) {
            int depth = getStackTraceDepth();
            stackTrace = new StackTraceElement[depth];
            for (int i=0; i < depth; i++)
                stackTrace[i] = getStackTraceElement(i);
        } else if (stackTrace == null) {
            return UNASSIGNED_STACK;
        }
        return stackTrace;
    }

貌似没有什么不对劲的地方哈,其中

int depth = getStackTraceDepth();
stackTrace[i] = getStackTraceElement(i);

两个方法都是native的,不予深究了。这里没有找到问题,就去看了下Thread的getStackTrace方法,代码如下:

public StackTraceElement[] getStackTrace() {
        if (this != Thread.currentThread()) {
            // check for getStackTrace permission
            SecurityManager security = System.getSecurityManager();
            if (security != null) {
                security.checkPermission(
                    SecurityConstants.GET_STACK_TRACE_PERMISSION);
            }
            // optimization so we do not call into the vm for threads that
            // have not yet started or have terminated
            if (!isAlive()) {
                return EMPTY_STACK_TRACE;
            }
            StackTraceElement[][] stackTraceArray = dumpThreads(new Thread[] {this});
            StackTraceElement[] stackTrace = stackTraceArray[0];
            // a thread that was alive during the previous isAlive call may have
            // since terminated, therefore not having a stacktrace.
            if (stackTrace == null) {
                stackTrace = EMPTY_STACK_TRACE;
            }
            return stackTrace;
        } else {
            // Don't need JVM help for current thread
            return (new Exception()).getStackTrace();
        }
    }

乍一看也没啥错啊~~~不对,突然间跟着逻辑走一下,在if语句里面因为

this != Thread.currentThread()是为true的,所以方法会执行else里面的语句,里面是啥!!!!

return (new Exception()).getStackTrace();

All right!就是这里,这里jvm去执行了new Exception().getStackTrace();就是这句话让使用Thread的getStackTrace方法就有可能多打印一句java.lang.Thread;getStackTrace;Thread.java出来!这也就是为什么使用

logger = LoggerFactory.getLogger(Thread.currentThread().getStackTrace()[1].getClassName());

会得不到正确的日志信息的原因了!

应该就是这样子了,为了验证我想的对不对,写了一个测试验证的例子,代码如下:

public class TestException {

	public static StackTraceElement[] getStackTrace() {
		return new Exception().getStackTrace();
	}
}
public class ThreadTest {

	public static void TestString(){
		StackTraceElement[] arr = TestException.getStackTrace();
		for(int i=0;i<=arr.length-1;i++){
			System.out.println(arr[i].getClassName()+";"+arr[i].getMethodName()+";"+arr[i].getFileName());
		}
	}
}
public class App
{
    public static void main( String[] args )
    {
        ThreadTest.TestString();
    }
}

执行之后的结果是:

test.TestException;getStackTrace;TestException.java

test.ThreadTest;TestString;ThreadTest.java

test.App;main;App.java

红色的一行即证明了我的想法是正确的!

所有的验证至此结束了,这个问题最后解决办法很简单,要么使用new Exception().getStackTrace()[1];要么使用Thread.currentThread().getStackTrace()[2]。

虽然这个问题很小,也很基础,但是我还是很兴奋,毕竟粘上了源码,顿时逼格升高不少~~~

新手上路,高手饶命~!

得到当前堆栈信息的两种方式(Thread和Throwable)的纠结

时间: 2024-10-17 08:41:57

得到当前堆栈信息的两种方式(Thread和Throwable)的纠结的相关文章

26.OpenIdConnect获取用户信息的两种方式

openId在OAuth基础之上,在下面这红框内拿到Authorization Code之后还可以返回IdToken. IdToken和AccessToken一起返回.IdToken就会包括了用户的信息Claims .通过我们的ProfileService返回回去. 也就是这里 设置为True了 ,就会把新用户的信息都包含在IdToken里面返回给用户,第三方拿到IdToken就可以用了不需要再去获取AccessToken,这是一种方式 另外一种方式是IdToken里面不包含用户的信息.它会有一

在Scrapy中如何利用Xpath选择器从HTML中提取目标信息(两种方式)

前一阵子我们介绍了如何启动Scrapy项目以及关于Scrapy爬虫的一些小技巧介绍,没来得及上车的小伙伴可以戳这些文章: 手把手教你如何新建scrapy爬虫框架的第一个项目(上) 手把手教你如何新建scrapy爬虫框架的第一个项目(下) 关于Scrapy爬虫项目运行和调试的小技巧(上篇) 关于Scrapy爬虫项目运行和调试的小技巧(下篇) 今天我们将介绍在Scrapy中如何利用Xpath选择器从HTML中提取目标信息.在Scrapy中,其提供了两种数据提取的方式,一种是Xpath选择器,一种是C

匿名内部类实现线程的两种方式

public static void main(String[] args) { //匿名内部类实现线程的两种方式 /*Thread t1 = new Thread(){ @Override public void run() { System.out.println("任务1...." + Thread.currentThread()); } }; t1.start();*/ new Thread(){ public void run() { System.out.println(&

遍历获得磁盘文件的两种方式

在winform中可能有这种情况,遍历某一个文件夹得到当前文件夹中的所有文件以及子文件夹中的所有文件,以此类推,然后添加到一个TreeView控件中,或者通过控制台输出文件以及文件夹的名称.方法多种多样,下面说的是通过递归和队列的方式来进行.递归其实就是在函数调用的时候进行压栈进行的,所以可以概述为通过栈和队列来实现. 递归方式实现 private void GetAllFile (string strPath,TreeNode parentNode) { //得到当前路径下的所有文件和文件夹

Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition

在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权.因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去.因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态.然后等待消费者消费了商品,然后消费者通知生产者队列有空间了.同样地,当

从源代码剖析Struts2中用户自定义配置转换器的两种方式——基于字段的配置转换器和基于类型的配置转换器(解决了实际系统中,因没有区分这两种工作方式的生命周期而引起的异常错误问题)

自定义类型转换器必须实现ongl.TypeConverter接口或对这个接口的某种具体实现做扩展 <<interface>>com.opensymphony.xwork2.conversion.TypeConverter à com.opensymphony.xwork2.conversion.impl.DefaultTypeConverter à org.apache.struts2.util.StrutsTypeConverter 接口及类进行解析 TypeConverter(

生成二维码的两种方式

利用qrcode生成二维码,(qrcode矩形二维码符号) 基于jquery的二维码生成插件qrcode,在页面中调用该插件就能生成对应的二维码.qrcode其实是通过使用jQuery实现图形渲染,画图,支持canvas(HTML5)和table两种方式: 使用插件时 1.首先在页面中加入jquery库文件和qrcode插件. <script type="text/javascript" src="jquery.js"></script> &

Centos7.3 下SQL Server 备份及还原的两种方式

Centos7.3 下SQL Server 备份及还原的两种方式 我们前面两篇文章介绍了Centos7.3下SQL Server的安装配置及使用Powershell的管理介绍,今天我们接着介绍如何实现Centos7.3 下SQL Server  备份及还原,有两种方式:1.使用SSMS备份及还原,该方式最为简单也最方便操作的方式,2.使用Linux下SQL Server自带功能命令备份,具体见下: 我们上一篇中创建了一个测试数据库,我们接着拿这个数据库进行测试,我们首先使用第一种方式,使用SSM

《连载 | 物联网框架ServerSuperIO教程》- 10持续传输大块数据流的两种方式(如:文件)

1.C#跨平台物联网通讯框架ServerSuperIO(SSIO)介绍 <连载 | 物联网框架ServerSuperIO教程>1.4种通讯模式机制. <连载 | 物联网框架ServerSuperIO教程>2.服务实例的配置参数说明 <连载 | 物联网框架ServerSuperIO教程>- 3.设备驱动介绍 <连载 | 物联网框架ServerSuperIO教程>-4.如开发一套设备驱动,同时支持串口和网络通讯. <连载 | 物联网框架ServerSupe