开发者

How to get the name of the calling class in Java?

I would like some help on this matter,

Example:

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

public class B extends A {}

public class C extends A {}

public class D {
     C.foo();
}

This is basically the scenario. My question is how can method foo() know who is calling it?

EDIT: Basically I am trying to do a database Layer, and in class A I will create a m开发者_C百科ethod that will generate SQL statements. Such statements are dynamically generated by getting the values of all the public properties of the calling class.


Easiest way is the following:

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

But in real there should be no need for this, unless for some logging purposes, because this is a fairly expensive task. What is it, the problem for which you think that this is the solution? We may come up with -much- better suggestions.

Edit: you commented as follows:

basically i'am trying to do a database Layer, and in Class A i will create a method that will generate sql statements, such statements are dynamically generated by getting the values of all the public properties of the calling class.

I then highly recommend to look for an existing ORM library, such as Hibernate, iBatis or any JPA implementation to your taste.


Java 9: Stack Walking API

JEP 259 provides an efficient standard API for stack walking that allows easy filtering of, and lazy access to, the information in stack traces. First off, you should obtain an instance of StackWalker:

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

StackWalker walker = StackWalker.getInstance(RETAIN_CLASS_REFERENCE);

After that you can call the getCallerClass() method:

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

Regardless of how you configured the StackWalker instance, the getCallerClass method will ignore the reflection frames, hidden frames and those are related to MethodHandles. Also, this method shouldn't be called on the first stack frame.


Perhaps for your use case it would make sense to pass the class of the caller into the method, like:

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

And call it something like this:

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


foo() is private, so the caller will always be in class A.


if you using slf4j as your application logging system. you can using:

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

I think it's faster than new Exception().getStackTrace(), since getStackTrace() alaways doing clone stacktrace.


I would use 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();
}

If you need the class of the calling method use skip=1.


From a stack trace: http://www.javaworld.com/javaworld/javatips/jw-javatip124.html


A hacky solution is sun.reflect.Reflection.getCallerClass.

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

It is hacky because you have to ensure that the class that calls Reflection.getCallerClass() is loaded on the bootstrap ClassLoader for the annotation @CallerSensitive (which getCallerClass is tagged with) to work. As such, it probably isn't the best solution for a project unless your project happens to use a Java Agent to add your classes to the bootstrap ClassLoader search.


With the following code, you obtain the first class which generated the stack of calls:

    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;
    }

Boolean argument is used to get the full name including package name, or just class name.


StackFrame

The state of one method invocation on a thread's call stack. As a thread executes, stack frames are pushed and popped from its call stack as methods are invoked and then return. A StackFrame mirrors one such frame from a target VM at some point in its thread's execution.

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 and Thread::getStackTrace return an array of StackTraceElement objects, which contain the class name and method name of each stack-trace element.

Throwable::getStackTrace contains the Stack with frames as Frame1(Top Frame) Current method, Frame2 calls Frame1 method for execution.

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

Thread::getStackTrace contains the stack with 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-internal sun.reflect.Reflection::getCallerClass method. It is deprecated, removed in Java9 JDK-8021946

Any way by using Reflection API we can't find the Line Number of Function which it get called.

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

Example:

    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");
    }
}

For Java 9 use Stack Walking API


I'm just answering this because for some reason the above answers started referring to exception handling - the original question had nothing to do with exceptions.

So, instead of trying to determine the caller of the method in question, and specifically to give more information dealing with the creation of a base class that generates SQL statements for its derived classes, here is an OO solution...

  1. Make the base class abstract and include abstract methods that return the data it needs to build a sql statement.

  2. This would include methods like...

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

  3. The base class then does not care who is calling it because it is going to call up to the derived class for all the details it needs to create the SQL statement. The base class knows the derived classes are going to provide the implementation of these methods because they are abstract.

Another way to implement this would be to have a SQLGenerator class that receives an interface with the methods described above and operates on the instances passed to it via those methods. For this, you would want to have the abstract methods described above moved into the interface, which all of your SQL related classes would implement.

List item


I tried this and it works well. It is because each Java Object has access to getClass() method which returns the class caller and the method name.

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

example usage:

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

sample output log using java.util.logging.Logger

Feb 01, 2017 11:14:50 PM rmg.data.model.DBTable (init) INFO: done


Maybe an answer is

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
  }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜