如何在 Java 中获取调用类的名称?

Posted

技术标签:

【中文标题】如何在 Java 中获取调用类的名称?【英文标题】:How to get the name of the calling class in Java? 【发布时间】:2010-12-14 08:53:38 【问题描述】:

我想在这件事上得到一些帮助,

例子:

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 语句的方法。此类语句是通过获取调用 class 的所有 public 属性的值来动态生成的。

【问题讨论】:

一种根据调用者的类改变其行为的方法确实彻底颠覆了面向对象的编程。您如何测试这样的类并使其在测试中的行为与生产中的行为相同?必须有更好的方法来实现你正在做的事情...... 如果这是为了记录/调试,也许你应该只使用调试器/告诉用户学习调试器而不是污染你的框架 这让我想起了 Fortran COME FROM 语句fortran.com/come_from.html 请添加注释,说明您需要使用它来获取公共字段(可能通过反射)。 这段代码毫无意义。 【参考方案1】:

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

因此,与其尝试确定相关方法的调用者,而是专门提供更多关于创建基类(为其派生类生成 SQL 语句)的信息,这里是一个面向对象的解决方案...

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

    这将包括...之类的方法

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

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

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

列表项

【讨论】:

【参考方案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
  

【讨论】:

【参考方案3】:

我试过了,效果很好。这是因为每个 Java Object 都可以访问 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) INFO:完成

【讨论】:

【参考方案4】:

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 相关的那些。此外,不应在第一个堆栈帧上调用此方法。

【讨论】:

【参考方案5】:

StackFrame

线程调用堆栈上的一个方法调用的状态。当线程执行时,堆栈帧被推入并从其调用堆栈中弹出,因为方法被调用然后返回。 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::getStackTraceThread::getStackTrace 返回一个 StackTraceElement 对象数组,其中包含每个堆栈跟踪元素的类名和方法名。

Throwable::getStackTrace 包含以 Frame1(Top Frame) 当前方法为 Frame1 的 Stack,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

【讨论】:

【参考方案6】:

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

    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;
    

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

【讨论】:

【参考方案7】:

我会使用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

【讨论】:

【参考方案8】:

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

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

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

【讨论】:

【参考方案9】:

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

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

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

【讨论】:

另外需要注意的是,包sun.reflect 表明它是一个内部API,不会保存在未来的版本中。理想情况下,不应依赖这些包中的类。【参考方案10】:

最简单的方法如下:

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

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

编辑:您的评论如下:

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

然后我强烈建议您根据自己的喜好寻找现有的ORM library,例如Hibernate、iBatis 或任何JPA implementation。

【讨论】:

基本上我正在尝试做一个数据库层,并且在A类中我将创建一个将生成sql语句的方法,这些语句是通过获取所有公共属性的值来动态生成的调用类。 @Mark:这真是糟糕的设计。我会深深地重新考虑它。 @Peter:Thread.currentThread().getStackTrace()[0].getMethodName() 始终是"getStackTrace"。猜猜你可以弄清楚为什么...... @Mark,这会给你类的名字,但不是实例。换句话说,您将从哪个对象获取公共字段。你应该传入一个数据对象。 @Peter:查看Thread#getStackTrace()的源代码。对...它执行new Exception().getStackTrace()【参考方案11】:

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

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

然后这样称呼它:

public class B  new A().foo(getClass() /* or: B.class */ ); 

【讨论】:

+1 表示 + 指出 正确的 方法。让我们不要为这样的事情弄乱堆栈跟踪。 是的。如果调用者必须追求使用反射来执行任务的基本设计,请让联系清晰。传递类或实例。 总的来说我同意你的观点,但如果你正在创建一个框架,它会变得有用 +1,使用堆栈跟踪的唯一原因是在调试场景中。 你为什么还要谈论 Class 参数?他需要 D 类型的对象,因此他可以读取属性值。我认为 Mark 还是混淆了 Class/Object,或者那里的一切都是 static 的?【参考方案12】:

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

【讨论】:

D 类不会编译。【参考方案13】:

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

【讨论】:

以上是关于如何在 Java 中获取调用类的名称?的主要内容,如果未能解决你的问题,请参考以下文章

java 当一个接口被多个类继承时 如何知道是调用的哪个类的实现

在 Freemarker 模板中,如何获取数据模型类的名称?

如何在javascript中获取类的私有属性名称? [复制]

Java - 调用get方法时获取类属性名称

java中如何通过反射获取类的属性

如何在C#中,在一个类里调用另外一个类的方法