24

我想在这个问题上得到一些帮助,

例子:

public class A {
    private void foo() {
        //Who invoked me?
    }
}

public class B extends A {}

public class C extends A {}

public class D {
     C.foo();
}

基本上就是这样的场景。我的问题是方法如何foo()知道谁在调用它?

编辑:基本上我正在尝试做一个数据库层,并且class A我将创建一个生成 SQL 语句的方法。此类语句是通过获取public调用的所有属性的值动态生成的class

4

13 回答 13

37

最简单的方法如下:

String className = new Exception().getStackTrace()[1].getClassName();

但实际上应该没有必要这样做,除非出于某些日志记录目的,因为这是一项相当昂贵的任务。它是什么,您认为这是解决方案的问题?我们可能会提出更好的建议。

编辑:您评论如下:

基本上我正在尝试做一个数据库层,并且在 A 类中我将创建一个生成 sql 语句的方法,这些语句是通过获取调用类的所有公共属性的值来动态生成的。

然后我强烈建议寻找现有的ORM 库,例如HibernateiBatis或任何符合您口味的JPA 实现。

于 2009-11-08T13:35:39.873 回答
20

Java 9:堆栈遍历 API

JEP 259为堆栈遍历提供了一个高效的标准 API,允许轻松过滤和延迟访问堆栈跟踪中的信息。首先,您应该获得一个实例StackWalker

import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;
// other imports

StackWalker walker = StackWalker.getInstance(RETAIN_CLASS_REFERENCE);

之后,您可以调用该getCallerClass()方法:

Class<?> callerClass = walker.getCallerClass();

无论您如何配置StackWalker实例,该getCallerClass方法都会忽略反射帧、隐藏帧以及与MethodHandles 相关的那些。此外,不应在第一个堆栈帧上调用此方法。

于 2017-08-22T07:07:05.260 回答
16

也许对于您的用例,将调用者的类传递给方法是有意义的,例如:

public class A { public void foo(Class<?> c) { ... } }

并称其为:

public class B { new A().foo(getClass() /* or: B.class */ ); }
于 2009-11-08T13:52:06.680 回答
3

foo() 是私有的,所以调用者总是在 A 类中。

于 2009-11-08T13:38:02.420 回答
2

如果您使用 slf4j 作为您的应用程序日志记录系统。你可以使用:

Class<?> source = org.slf4j.helpers.Util.getCallingClass();

我认为它比 new Exception().getStackTrace() 更快,因为 getStackTrace() 总是在执行克隆堆栈跟踪。

于 2018-01-12T05:32:12.810 回答
1

从堆栈跟踪:http ://www.javaworld.com/javaworld/javatips/jw-javatip124.html

于 2009-11-08T13:35:59.513 回答
1

我会使用StackWalker

private static Class<?> getCallingClass(int skip) {
    StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
    Optional<? extends Class<?>> caller = walker.walk(frames ->
            frames.skip(skip).findFirst().map(StackWalker.StackFrame::getDeclaringClass)
    );
    return caller.get();
}

如果您需要调用方法的类,请使用skip=1.

于 2018-12-02T10:29:03.397 回答
0

使用以下代码,您可以获得生成调用堆栈的第一个类:

    public String getInvonkingClassName(boolean fullClassNameNeeded){

        StackTraceElement[] stack = new Exception().getStackTrace();
        String className = stack[stack.length-1].getClassName();


        if(!fullClassNameNeeded){
            int idx = className.lastIndexOf('.');
            className = className.substring(idx+1);
        }

        return className;
    }

布尔参数用于获取包括包名在内的全名,或者只是类名。

于 2019-01-17T15:14:20.977 回答
0

堆栈帧

线程调用堆栈上的一个方法调用的状态。当线程执行时,堆栈帧被推入并从其调用堆栈中弹出,因为方法被调用然后返回。StackFrame 在其线程执行的某个时间点从目标 VM 镜像一个这样的帧。

JVM Stack: From Frame 1 get Frame 2 details
    |                                           |
    |                                           |
    | Class2.function1()             [FRAME 1]  |
    |       executing the instructions          |
    |-------------------------------------------|
    |Class1.method1()                [FRAME 2]  |
    | called for execution Class2.function1()   |
    |-------------------------------------------|

Throwable::getStackTrace并返回一个StackTraceElementThread::getStackTrace对象数组 ,其中包含每个堆栈跟踪元素的类名和方法名。

Throwable::getStackTrace包含带有帧的堆栈作为 Frame1(Top Frame) 当前方法,Frame2 调用 Frame1 方法执行。

StackTraceElement[] stackTraceElements = (new Throwable()).getStackTrace();
// Frame1:Log4J.log(), Frame2:CallerClass

Thread::getStackTrace包含带有 Frames 的堆栈:
Frame1:Thread.getStackTrace(), Frame2:Current Method, Frame3:Caller Method

StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); // 

sun.misc.SharedSecrets.getJavaLangAccess()

sun.misc.JavaLangAccess javaLangAccess = sun.misc.SharedSecrets.getJavaLangAccess();
StackTraceElement frame = javaLangAccess.getStackTraceElement((new Throwable()), callerFrame-1 ); // Frame0:Log4J.log(), Frame1:CallerClass
System.out.format("SUN - Clazz:%s, Method:%s, Line:%d\n", frame.getClassName(), frame.getMethodName(), frame.getLineNumber());

Throwable throwable = new Throwable();
int depth = javaLangAccess.getStackTraceDepth(new Throwable());
System.out.println("\tsun.misc.SharedSecrets : "+javaLangAccess.getClass() + " - StackTraceDepth : "+ depth);
for (int i = 0; i < depth; i++) {
    StackTraceElement frame = javaLangAccess.getStackTraceElement(throwable, i);
    System.out.format("Clazz:%s, Method:%s, Line:%d\n", frame.getClassName(), frame.getMethodName(), frame.getLineNumber());
}

JDK 内部sun.reflect.Reflection::getCallerClass方法。它已被弃用,在 Java9 JDK-8021946中被删除

通过使用反射 API 的任何方式,我们都找不到它被调用的函数的行号。

System.out.println("Reflection - Called from Clazz : "+ Reflection.getCallerClass( callerFrame )); // Frame1:Log4J.log(), Frame2:CallerClass

例子:

    static boolean log = false;

    public static void log(String msg) {
        int callerFrame = 2; // Frames [Log4J.log(), CallerClass.methodCall()] 
        StackTraceElement callerFrameStack = null;

        StackTraceElement[] stackTraceElements = (new Throwable()).getStackTrace(); // Frame1:Log4J.log(), Frame2:CallerClass
        //StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();// Frame1:Thread.getStackTrace(), Frame2:Log4J.log(), Frame3:CallerClass
        int callerMethodFrameDepth = callerFrame; // Caller Class Frame = Throwable:2(callerFrame), Thread.currentThread:2(callerFrame+1)
        for (int i = 0; i < stackTraceElements.length; i++) {
            StackTraceElement threadFrame = stackTraceElements[i];
            if (i+1 == callerMethodFrameDepth) {
                callerFrameStack = threadFrame;
                System.out.format("Called form Clazz:%s, Method:%s, Line:%d\n", threadFrame.getClassName(), threadFrame.getMethodName(), threadFrame.getLineNumber());
            }
        }

        System.out.println(msg);
        if (!log){
            Logger logger = Logger.getLogger(callerFrameStack.getClass());
            logger.info(msg);
        }
    }

    public static void main(String[] args) {
        Log4J.log("Log4J, main");
        Clazz1.mc1();
        Clazz21.mc12();
        Clazz21.mc11();
        Clazz21.mc21();
    }
}

class Clazz1 {
    public static void mc1() {
        Log4J.log("Clazz1 - mc1");
    }
}
class Clazz11 {
    public static void mc11() {
        Log4J.log("Clazz11 - mc11");
    }
    public static void mc12() {
        Log4J.log("Clazz11 - mc12");
        Clazz1.mc1();
    }
}
class Clazz21 extends Clazz11 {
    public static void mc21() {
        Log4J.log("Clazz21 - mc21");
    }
}

对于 Java 9,使用Stack Walking API

于 2019-07-17T17:24:47.383 回答
0

我只是在回答这个问题,因为出于某种原因,上述答案开始提到异常处理——最初的问题与异常无关。

因此,与其试图确定所讨论方法的调用者,不如专门提供更多关于创建为其派生类生成 SQL 语句的基类的信息,这里是一个 OO 解决方案......

  1. 使基类抽象并包含抽象方法,这些方法返回构建 sql 语句所需的数据。

  2. 这将包括诸如...之类的方法

    getColumnList() getFromTable() getJoinedTables() getFilterColumns()

  3. 然后,基类不关心谁在调用它,因为它会调用派生类以获取创建 SQL 语句所需的所有细节。基类知道派生类将提供这些方法的实现,因为它们是抽象的。

实现这一点的另一种方法是拥有一个 SQLGenerator 类,该类接收带有上述方法的接口,并对通过这些方法传递给它的实例进行操作。为此,您可能希望将上述抽象方法移到接口中,您的所有 SQL 相关类都将实现该接口。

项目清单

于 2021-12-21T00:15:36.490 回答
0

一个 hacky 解决方案是sun.reflect.Reflection.getCallerClass.

public void foo() {
    Class<?> caller = sun.reflect.Reflection.getCallerClass();
    // ...
}

这很 hacky,因为您必须确保调用的类Reflection.getCallerClass()加载到引导类加载器上,以便注释@CallerSensitivegetCallerClass带有标记)工作。因此,它可能不是项目的最佳解决方案,除非您的项目碰巧使用Java 代理将您的类添加到引导类加载器搜索中。

于 2017-08-18T23:05:18.847 回答
-1

我试过了,效果很好。这是因为每个 JavaObject都可以访问getClass()返回class调用者和方法名称的方法。

public Logger logger() {
    return Logger.getLogger(getClass().toString());
}

示例用法:

public DBTable(String tableName) {
    this.tableName = tableName;
    loadTableField();
    this.logger().info("done");
}

使用示例输出日志java.util.logging.Logger

2017 年 2 月 1 日晚上 11:14:50 rmg.data.model.DBTable (init) 信息:完成

于 2017-02-01T15:25:09.987 回答
-2

也许答案是

public class CallerMain {

  public void foo(){
    System.out.println("CallerMain - foo");
    System.out.println(this.getClass()); //output- callerMain
  }
  public static void main(String[] args) {
    A a = new A();
    CallerMain cm = new CallerMain();
    cm.foo();
  }
}

class A{
  public void foo(){
    System.out.println("A - foo");
    System.out.println(this.getClass());//output- A
  }
}
于 2013-11-25T20:23:33.350 回答