Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get multi-signature caller method with reflection?

Suppose we have:

@WebserviceUrl("/withObj")
public void caller(Object obj){
      called();
}

@WebserviceUrl("/withoutObj")
public void caller(){
      called();
}

as you can see, caller has two signatures. In order to get the stack trace we can use:

StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();

But it only returns the name of the method. How can I find the actual true caller?

UPDATE:
The main intention of the question is to read the webservice url which is declared in the method's annotation. wrong detection of caller method, cause calling the wrong webservice.

like image 752
Khoda Avatar asked Jul 25 '15 11:07

Khoda


2 Answers

Interesting. A possible approach I would think of is doing the same as a human being would do : read the line number in the stacktrace and then go to the class. It seems it is doable like this : How to get the line number of a method?. This is not directly applicable because CtClass.getDeclaredMethod only gives you one signature of the method. Yet, you can do something like this :

String className;
String methodName;
int lineNumber;
// parse the stacktrace to get the name of the class, the name of the method and its line number

ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get(className);
CtMethod methodWhereExceptionOccurred = 
    Stream.of(cc.getDeclaredMethods())
          .filter(method -> method.getName().equals(methodName))
          .filter(method -> method.getMethodInfo().getLineNumber(0) == lineNumber)
          .findFirst()
          .get();
like image 90
Dici Avatar answered Sep 29 '22 16:09

Dici


Answer above beat me to it, but I'd add that you might have to go through several stack frames before you get the class or method with the annotations you are looking for, even if the source code is in the same class (but you probably know this).

For example, here was my "Test" class...

import javassist.CtMethod;

public class Test {

    private void method(String s) {
        called();
    }

    private void method() {
        called();
    }

    private static void called() {
        CtMethod caller = StackTraceUtil.getCaller();
        System.out.println(caller);
    }

    private interface Widgit {
        void call();
    }

    private static void call(Widgit w) {
        w.call();
    }

    public static void main(String[] args) {
        new Test().method();
        new Test().method("[]");
        new Widgit() {
            @Override
            public void call() {
                called();
            }
        }.call();
        call(() -> {
            called();
        });
    }
}

The output from this was...

javassist.CtMethod@e59521a2[private method ()V]
javassist.CtMethod@abb88b98[private method (Ljava/lang/String;)V]
javassist.CtMethod@bbd779f1[static access$0 ()V]
javassist.CtMethod@67f92ed4[private static lambda$0 ()V]

Here's StackTraceUtil for completeness. You see that I hard-coded "3" to get the the stace trace element from the array. You probably need to loop through everything from element 3 onwards until you find your annotation.

(This is not as elegant as the answer above, but as I'd nearly finished it I thought I'd post it anyway...)

import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;

public class StackTraceUtil {

    private static Map<Class<?>, SortedMap<Integer, CtMethod>> cache = new HashMap<>();

    public static CtMethod getCaller() {
        StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
        StackTraceElement stackTraceElement = stacktrace[3];
        int lineNumber = stackTraceElement.getLineNumber();
        try {
            return findMethod(Class.forName(stackTraceElement.getClassName()), lineNumber);
        } catch (ClassNotFoundException e) {
            return null;
        }
    }

    public static CtMethod findMethod(Class<?> clazz, int lineNumber) {
        SortedMap<Integer, CtMethod> classInfo = cache.get(clazz);

        if (classInfo == null) {
            classInfo = populateClass(clazz);
            cache.put(clazz, classInfo);
        }

        if(classInfo != null) {
            SortedMap<Integer, CtMethod> map = classInfo.tailMap(lineNumber);

            if(!map.isEmpty()) {
                return map.values().iterator().next();
            }
        }

        return null;
    }

    private static SortedMap<Integer, CtMethod> populateClass(Class<?> clazz) {
        SortedMap<Integer, CtMethod> result;

        try {
            ClassPool pool = ClassPool.getDefault();
            CtClass cc = pool.get(clazz.getCanonicalName());
            CtMethod[] methods = cc.getDeclaredMethods();

            result = new TreeMap<>();

            for (CtMethod ctMethod : methods) {
                result.put(ctMethod.getMethodInfo().getLineNumber(0), ctMethod);
            }
        } catch (NotFoundException ex) {
            result = null;
        }

        return result;
    }
}

Hope this helps.

like image 24
BretC Avatar answered Sep 29 '22 18:09

BretC