Java获取调用当前方法的类名或方法名(栈堆信息)的4种方式

在java代码中,是可以在运行时通过某种方式获取到当前方法被谁调用了(调用链路)。目前我所知道的有四种方式(通过Thread、Throwable、SecurityManager获取),下面逐个列出,附有代码和截图。

  1. Thread.getAllStackTraces()方式
    Thread.getAllStackTraces()可以获取所有线程的栈堆跟踪信息,返回值是Map<Thread, StackTraceElement[]>类型,可以通过Thread.currentThread()拿到当前Thread对象,从map集合中获取栈堆跟踪信息。StackTraceElement对象可以拿到调用者的类名、Class对象、调用的代码所在行数等。
    代码如下:

    public class A {
        public static void main(String[] args) {
            B.print();
        }
    }
    
    class B {
        public static void print() {
            System.out.println("调用B#print");
            C.print();
        }
    }
    
    class C {
        public static void print() {
            System.out.println("调用C#print");
            // 获取所有线程的栈堆跟踪集合
            Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();
            // 去map集合里面拿当前线程的栈堆跟踪信息数组
            StackTraceElement[] stackTraceElements = allStackTraces.get(Thread.currentThread());
            System.out.println(Arrays.toString(stackTraceElements));
        }
    }
    
    

    在这里插入图片描述在这里插入图片描述
    可以看到,下标为4的StackTraceElement对象就是我们的main方法,之所以出现了下标0、1非自定义类的栈堆信息是因为调用了Thread.getAllStackTraces()方法,方法以及方法内部也执行了其他代码。

  2. Thread.currentThread().getStackTrace()方式
    通过Thread.currentThread()拿到当前的线程,再通过Thread对象的getStackTrace()方法获取线程的栈堆跟踪信息,getStackTrace()非static方法,所以要先拿到Thread对象。第一种和第二种方式实际上都是调用了private native static StackTraceElement[][] dumpThreads(Thread[] threads)本地方法来获取。这两种方式获取的话,栈堆跟踪信息数组里面都有非自定义类的调用信息(看下标0)。
    代码如下:

    public class A {
        public static void main(String[] args) {
            B.print();
            System.out.println(Arrays.toString(Thread.currentThread().getStackTrace()));
        }
    }
    
    class B {
        public static void print() {
            System.out.println("调用B#print");
            C.print();
        }
    }
    
    class C {
        public static void print() {
            // 获取当前线程,再获取栈堆跟踪数组
            StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
            System.out.println(Arrays.toString(stackTrace));
        }
    }
    
    

    在这里插入图片描述

  3. new RuntimeException().getStackTrace()方式
    实际上,只要是Throwable的子类包括他自身都可以,内部也是调用了一个本地方法来获取的栈堆信息。SpringApplication类中的deduceMainApplicationClass()方法就是通过这种方式来拿主类的Class对象。拿到了之后就可以通过for循环整个数组通过匹配方法名或者类型来拿想要信息了,或者直接通过整个数组拿到整个调用链路。
    代码如下:

    public class A {
        public static void main(String[] args) {
            B.print();
        }
    }
    
    class B {
        public static void print() {
            System.out.println("调用B#print");
            C.print();
        }
    }
    
    class C {
        public static void print() {
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            System.out.println(Arrays.toString(stackTrace));
        }
    }
    

    在这里插入图片描述
    在这里插入图片描述

  4. SecurityManager方式(这种可以拿到类信息,但是拿不到栈堆信息)
    SecurityManager中有一个getClassContext()方法,这个方法可以获取到调用当前方法的Class信息,返回的类型是一个Class数组,和前面几种不同的是,这种方式没法获取代码行数、调用的方法等(Class对象大家应该很熟悉了,能拿到哪种信息应该都知道)。
    由于getClassContext方法是非static的,所以要通过对象的方式调用,但是不能通过new SecurityManager()的对象来获取,因为这个方法是一个protected方法。protected native Class[] getClassContext()。所以需要自定义一个类,继承SecurityManager类,然后重写getClassContext方法,这样我们就能在这个类中使用这个方法了。
    这种方式在slf4j中Util#getCallingClass()用到了
    代码如下:

    public class A {
        public static void main(String[] args) {
            B.print();
        }
    }
    
    class B {
        public static void print() {
            System.out.println("调用B#print");
            C.print();
        }
    }
    
    class C {
    
        public static void print() {
            // 获取一个安全管理器对象
            ClassContextSecurityManager securityManager = getSecurityManager();
            if (securityManager != null) {
                System.out.println(Arrays.toString(securityManager.getClassContext()));
            }
        }
    
        static ClassContextSecurityManager getSecurityManager() {
            try {
                return new ClassContextSecurityManager();
            } catch (Throwable throwable) {
                return null;
            }
        }
    
        /**
         * 自定义的安全管理器,继承java.lang.SecurityManager类
         */
        private static final class ClassContextSecurityManager extends SecurityManager {
        	/**
        	 * 重写getClassContext方法,不然方法在SecurityManager中又被protected修饰,其他类还是调用不了
        	 */
        	@Override
            protected Class<?>[] getClassContext() {
                return super.getClassContext();
            }
        }
    }
    

    在这里插入图片描述

  • 20
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值