Get callers method (java.lang.reflect.Method)

后端 未结 4 456
礼貌的吻别
礼貌的吻别 2020-12-08 16:53

I would like to get the calling method java.lang.reflect.Method. NOT the name of the method.

Here is an example how to get the callers

相关标签:
4条回答
  • 2020-12-08 17:12

    We can almost get there, here's a method that works in many cases. The problem is: it won't work reliably if there are overloaded methods (multiple methods with the same name). The stack trace does not provide the arguments, unfortunately.

    private static Method getCallingMethod() throws ClassNotFoundException{
        final Thread t = Thread.currentThread();
        final StackTraceElement[] stackTrace = t.getStackTrace();
        final StackTraceElement ste = stackTrace[2];
        final String methodName = ste.getMethodName();
        final String className = ste.getClassName();
        Class<?> kls = Class.forName(className);
        do{
            for(final Method candidate : kls.getDeclaredMethods()){
                if(candidate.getName().equals(methodName)){
                    return candidate;
                }
            }
            kls = kls.getSuperclass();
        } while(kls != null);
        return null;
    }
    

    Test code:

    public static void main(final String[] args) throws Exception{
        System.out.println(getCallingMethod());
    }
    

    Output:

    public static void foo.bar.Phleem.main(java.lang.String[]) throws java.lang.Exception


    OK, here is a solution using ASM. It works for almost all cases:

    private static Method getCallingMethod() throws ClassNotFoundException,
        IOException{
        final Thread t = Thread.currentThread();
        final StackTraceElement[] stackTrace = t.getStackTrace();
        final StackTraceElement ste = stackTrace[2];
        final String methodName = ste.getMethodName();
        final int lineNumber = ste.getLineNumber();
        final String className = ste.getClassName();
        final Class<?> kls = Class.forName(className);
        final ClassReader cr = new ClassReader(className);
        final EmptyVisitor empty = new EmptyVisitor();
        final AtomicReference<Method> holder = new AtomicReference<Method>();
    
        cr.accept(new ClassAdapter(empty){
    
            @Override
            public MethodVisitor visitMethod(
    
            final int access,
                final String name,
                final String desc,
                final String signature,
                final String[] exceptions){
    
                return name.equals(methodName) ? new MethodAdapter(empty){
    
                    @Override
                    public void visitLineNumber(final int line,
                        final Label start){
                        if(line >= lineNumber && holder.get() == null){
    
                            final Type[] argumentTypes =
                                Type.getArgumentTypes(desc);
                            final Class<?>[] argumentClasses =
                                new Class[argumentTypes.length];
                            try{
                                for(int i = 0; i < argumentTypes.length; i++){
                                    final Type type = argumentTypes[i];
                                    final String dd = type.getDescriptor();
    
                                    argumentClasses[i] = getClassFromType(type);
                                }
                                holder.set(kls.getDeclaredMethod(methodName,
                                    argumentClasses));
                            } catch(final ClassNotFoundException e){
                                throw new IllegalStateException(e);
                            } catch(final SecurityException e){
                                throw new IllegalStateException(e);
                            } catch(final NoSuchMethodException e){
                                throw new IllegalStateException(e);
                            }
                        }
                        super.visitLineNumber(line, start);
                    }
    
                    private Class<?> getClassFromType(final Type type) throws ClassNotFoundException{
                        Class<?> javaType;
                        final String descriptor = type.getDescriptor();
                        if(type.equals(Type.INT_TYPE)){
                            javaType = Integer.TYPE;
                        } else if(type.equals(Type.LONG_TYPE)){
                            javaType = Long.TYPE;
                        } else if(type.equals(Type.DOUBLE_TYPE)){
                            javaType = Double.TYPE;
                        } else if(type.equals(Type.FLOAT_TYPE)){
                            javaType = Float.TYPE;
                        } else if(type.equals(Type.BOOLEAN_TYPE)){
                            javaType = Boolean.TYPE;
                        } else if(type.equals(Type.BYTE_TYPE)){
                            javaType = Byte.TYPE;
                        } else if(type.equals(Type.CHAR_TYPE)){
                            javaType = Character.TYPE;
                        } else if(type.equals(Type.SHORT_TYPE)){
                            javaType = Short.TYPE;
                        } else if(descriptor.startsWith("[")){
                            final Class<?> elementType =
                                getClassFromType(type.getElementType());
                            javaType =
                                Array.newInstance(elementType, 0).getClass();
    
                        } else{
                            javaType = Class.forName(type.getClassName());
                        }
                        return javaType;
                    }
                }
                    : null;
            }
        },
            0);
        return holder.get();
    
    }
    

    I'll leave it to you to refactor this into something readable. And it won't work if the signature of the calling method contains primitive arrays or multidimensional arrays. Obviously it only works if the class file contains line numbers.

    Argghh, I work for ages and then I see that someone has come up with an almost identical solution!!! Anyway, I'll leave mine, because I developed it independently.

    0 讨论(0)
  • 2020-12-08 17:13

    Here is a modified version of Sean Patrick Floyd's posted method for getting a Java Class from an ASM Type. It fixes a problem with multidimensional arrays and another problem with classes loaded by other classloaders.

    public static Class<?> getClassFromType(Class<?> clazz, final Type type) throws ClassNotFoundException{
        Class<?> javaType = null;
        switch( type.getSort() ) {
            case Type.VOID      : javaType = Void.TYPE; break;
            case Type.BOOLEAN   : javaType = Boolean.TYPE; break;
            case Type.CHAR      : javaType = Character.TYPE; break;
            case Type.BYTE      : javaType = Byte.TYPE; break;
            case Type.SHORT     : javaType = Short.TYPE; break;
            case Type.INT       : javaType = Integer.TYPE; break;
            case Type.FLOAT     : javaType = Float.TYPE; break;
            case Type.LONG      : javaType = Long.TYPE; break;
            case Type.DOUBLE    : javaType = Double.TYPE; break;
            case Type.ARRAY     : javaType = Array.newInstance( getClassFromType( clazz, type.getElementType()), new int[type.getDimensions()] ).getClass(); break; 
            case Type.OBJECT    : javaType = Class.forName( type.getClassName(), false, clazz.getClassLoader() ); break;
        }
        if ( javaType != null ) return javaType;
        throw new ClassNotFoundException( "Couldn't find class for type " + type );
    }
    
    0 讨论(0)
  • 2020-12-08 17:24

    If it's just for testing, then this may work. It assumes that the class files are accessible via the calling class's ClassLoader and that the class files were compiled with debugging symbols (which I hope they are for testing!). This code relies on the ASM bytecode library.

    public static Method getMethod(final StackTraceElement stackTraceElement) throws Exception {
        final String stackTraceClassName = stackTraceElement.getClassName();
        final String stackTraceMethodName = stackTraceElement.getMethodName();
        final int stackTraceLineNumber = stackTraceElement.getLineNumber();
        Class<?> stackTraceClass = Class.forName(stackTraceClassName);
    
        // I am only using AtomicReference as a container to dump a String into, feel free to ignore it for now
        final AtomicReference<String> methodDescriptorReference = new AtomicReference<String>();
    
        String classFileResourceName = "/" + stackTraceClassName.replaceAll("\\.", "/") + ".class";
        InputStream classFileStream = stackTraceClass.getResourceAsStream(classFileResourceName);
    
        if (classFileStream == null) {
            throw new RuntimeException("Could not acquire the class file containing for the calling class");
        }
    
        try {
            ClassReader classReader = new ClassReader(classFileStream);
            classReader.accept(
                    new EmptyVisitor() {
                        @Override
                        public MethodVisitor visitMethod(int access, final String name, final String desc, String signature, String[] exceptions) {
                            if (!name.equals(stackTraceMethodName)) {
                                return null;
                            }
    
                            return new EmptyVisitor() {
                                @Override
                                public void visitLineNumber(int line, Label start) {
                                    if (line == stackTraceLineNumber) {
                                        methodDescriptorReference.set(desc);
                                    }
                                }
                            };
                        }
                    },
                    0
                );
        } finally {
            classFileStream.close();
        }
    
        String methodDescriptor = methodDescriptorReference.get();
    
        if (methodDescriptor == null) {
            throw new RuntimeException("Could not find line " + stackTraceLineNumber);
        }
    
        for (Method method : stackTraceClass.getMethods()) {
            if (stackTraceMethodName.equals(method.getName()) && methodDescriptor.equals(Type.getMethodDescriptor(method))) {
                return method;
            }
        }
    
        throw new RuntimeException("Could not find the calling method");
    }
    
    0 讨论(0)
  • 2020-12-08 17:30

    quite easy: just get the corresponding Class object first and then use Class.getMethod(String name,params...)

    check here for the javadoc

    public class GetMethod {
        public static void main(String[] args){
            new GetMethod().checkMethod();
        }
    
        public void checkMethod(){
            Thread t=Thread.currentThread();
            StackTraceElement element=t.getStackTrace()[1];
            System.out.println(element.getClassName());
            System.out.println(element.getMethodName());
            try{
                Method m=Class.forName(element.getClassName()).getMethod(element.getMethodName(),null);
                System.out.println("Method: " + m.getName());
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    hope that helped....

    0 讨论(0)
提交回复
热议问题