How do I find the caller of a method using stacktrace or reflection?

后端 未结 12 1151
鱼传尺愫
鱼传尺愫 2020-11-21 13:26

I need to find the caller of a method. Is it possible using stacktrace or reflection?

相关标签:
12条回答
  • 2020-11-21 13:50

    Sounds like you're trying to avoid passing a reference to this into the method. Passing this is way better than finding the caller through the current stack trace. Refactoring to a more OO design is even better. You shouldn't need to know the caller. Pass a callback object if necessary.

    0 讨论(0)
  • 2020-11-21 13:52

    I've done this before. You can just create a new exception and grab the stack trace on it without throwing it, then examine the stack trace. As the other answer says though, it's extremely costly--don't do it in a tight loop.

    I've done it before for a logging utility on an app where performance didn't matter much (Performance rarely matters much at all, actually--as long as you display the result to an action such as a button click quickly).

    It was before you could get the stack trace, exceptions just had .printStackTrace() so I had to redirect System.out to a stream of my own creation, then (new Exception()).printStackTrace(); Redirect System.out back and parse the stream. Fun stuff.

    0 讨论(0)
  • 2020-11-21 13:56
         /**
           * Get the method name for a depth in call stack. <br />
           * Utility function
           * @param depth depth in the call stack (0 means current method, 1 means call method, ...)
           * @return method name
           */
          public static String getMethodName(final int depth)
          {
            final StackTraceElement[] ste = new Throwable().getStackTrace();
    
            //System. out.println(ste[ste.length-depth].getClassName()+"#"+ste[ste.length-depth].getMethodName());
            return ste[ste.length - depth].getMethodName();
          }
    

    For example, if you try to get the calling method line for debug purpose, you need to get past the Utility class in which you code those static methods:
    (old java1.4 code, just to illustrate a potential StackTraceElement usage)

            /**
              * Returns the first "[class#method(line)]: " of the first class not equal to "StackTraceUtils". <br />
              * From the Stack Trace.
              * @return "[class#method(line)]: " (never empty, first class past StackTraceUtils)
              */
            public static String getClassMethodLine()
            {
                return getClassMethodLine(null);
            }
    
            /**
              * Returns the first "[class#method(line)]: " of the first class not equal to "StackTraceUtils" and aclass. <br />
              * Allows to get past a certain class.
              * @param aclass class to get pass in the stack trace. If null, only try to get past StackTraceUtils. 
              * @return "[class#method(line)]: " (never empty, because if aclass is not found, returns first class past StackTraceUtils)
              */
            public static String getClassMethodLine(final Class aclass)
            {
                final StackTraceElement st = getCallingStackTraceElement(aclass);
                final String amsg = "[" + st.getClassName() + "#" + st.getMethodName() + "(" + st.getLineNumber()
                +")] <" + Thread.currentThread().getName() + ">: ";
                return amsg;
            }
    
         /**
           * Returns the first stack trace element of the first class not equal to "StackTraceUtils" or "LogUtils" and aClass. <br />
           * Stored in array of the callstack. <br />
           * Allows to get past a certain class.
           * @param aclass class to get pass in the stack trace. If null, only try to get past StackTraceUtils. 
           * @return stackTraceElement (never null, because if aClass is not found, returns first class past StackTraceUtils)
           * @throws AssertionFailedException if resulting statckTrace is null (RuntimeException)
           */
          public static StackTraceElement getCallingStackTraceElement(final Class aclass)
          {
            final Throwable           t         = new Throwable();
            final StackTraceElement[] ste       = t.getStackTrace();
            int index = 1;
            final int limit = ste.length;
            StackTraceElement   st        = ste[index];
            String              className = st.getClassName();
            boolean aclassfound = false;
            if(aclass == null)
            {
                aclassfound = true;
            }
            StackTraceElement   resst = null;
            while(index < limit)
            {
                if(shouldExamine(className, aclass) == true)
                {
                    if(resst == null)
                    {
                        resst = st;
                    }
                    if(aclassfound == true)
                    {
                        final StackTraceElement ast = onClassfound(aclass, className, st);
                        if(ast != null)
                        {
                            resst = ast;
                            break;
                        }
                    }
                    else
                    {
                        if(aclass != null && aclass.getName().equals(className) == true)
                        {
                            aclassfound = true;
                        }
                    }
                }
                index = index + 1;
                st        = ste[index];
                className = st.getClassName();
            }
            if(resst == null) 
            {
                //Assert.isNotNull(resst, "stack trace should null"); //NO OTHERWISE circular dependencies 
                throw new AssertionFailedException(StackTraceUtils.getClassMethodLine() + " null argument:" + "stack trace should null"); //$NON-NLS-1$
            }
            return resst;
          }
    
          static private boolean shouldExamine(String className, Class aclass)
          {
              final boolean res = StackTraceUtils.class.getName().equals(className) == false && (className.endsWith("LogUtils"
                ) == false || (aclass !=null && aclass.getName().endsWith("LogUtils")));
              return res;
          }
    
          static private StackTraceElement onClassfound(Class aclass, String className, StackTraceElement st)
          {
              StackTraceElement   resst = null;
              if(aclass != null && aclass.getName().equals(className) == false)
              {
                  resst = st;
              }
              if(aclass == null)
              {
                  resst = st;
              }
              return resst;
          }
    
    0 讨论(0)
  • 2020-11-21 13:57
    StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace()
    

    According to the Javadocs:

    The last element of the array represents the bottom of the stack, which is the least recent method invocation in the sequence.

    A StackTraceElement has getClassName(), getFileName(), getLineNumber() and getMethodName().

    You will have to experiment to determine which index you want (probably stackTraceElements[1] or [2]).

    0 讨论(0)
  • 2020-11-21 13:57

    This method does the same thing but a little more simply and possibly a little more performant and in the event you are using reflection, it skips those frames automatically. The only issue is it may not be present in non-Sun JVMs, although it is included in the runtime classes of JRockit 1.4-->1.6. (Point is, it is not a public class).

    sun.reflect.Reflection
    
        /** Returns the class of the method <code>realFramesToSkip</code>
            frames up the stack (zero-based), ignoring frames associated
            with java.lang.reflect.Method.invoke() and its implementation.
            The first frame is that associated with this method, so
            <code>getCallerClass(0)</code> returns the Class object for
            sun.reflect.Reflection. Frames associated with
            java.lang.reflect.Method.invoke() and its implementation are
            completely ignored and do not count toward the number of "real"
            frames skipped. */
        public static native Class getCallerClass(int realFramesToSkip);
    

    As far as what the realFramesToSkip value should be, the Sun 1.5 and 1.6 VM versions of java.lang.System, there is a package protected method called getCallerClass() which calls sun.reflect.Reflection.getCallerClass(3), but in my helper utility class I used 4 since there is the added frame of the helper class invocation.

    0 讨论(0)
  • 2020-11-21 13:57

    Here is a part of the code that I made based in the hints showed in this topic. Hope it helps.

    (Feel free to make any suggestions to improve this code, please tell me)

    The counter:

    public class InstanceCount{
        private static Map<Integer, CounterInstanceLog> instanceMap = new HashMap<Integer, CounterInstanceLog>();
    private CounterInstanceLog counterInstanceLog;
    
    
        public void count() {
            counterInstanceLog= new counterInstanceLog();
        if(counterInstanceLog.getIdHashCode() != 0){
        try {
            if (instanceMap .containsKey(counterInstanceLog.getIdHashCode())) {
             counterInstanceLog= instanceMap .get(counterInstanceLog.getIdHashCode());
        }
    
        counterInstanceLog.incrementCounter();
    
                instanceMap .put(counterInstanceLog.getIdHashCode(), counterInstanceLog);
        }
    
        (...)
    }
    

    And the object:

    public class CounterInstanceLog{
        private int idHashCode;
        private StackTraceElement[] arrayStackTraceElements;
        private int instanceCount;
        private String callerClassName;
    
        private StackTraceElement getProjectClasses(int depth) {
          if(depth< 10){
            getCallerClassName(sun.reflect.Reflection.getCallerClass(depth).getName());
            if(getCallerClassName().startsWith("com.yourproject.model")){
                setStackTraceElements(Thread.currentThread().getStackTrace());
                setIdHashCode();
            return arrayStackTraceElements[depth];
            }
            //+2 because one new item are added to the stackflow
            return getProjectClasses(profundidade+2);           
          }else{
            return null;
          }
        }
    
        private void setIdHashCode() {
            if(getNomeClasse() != null){
                this.idHashCode = (getCallerClassName()).hashCode();
            }
        }
    
        public void incrementaContador() {
        this.instanceCount++;
    }
    
        //getters and setters
    
        (...)
    
    
    
    }
    
    0 讨论(0)
提交回复
热议问题