Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Java / Java8

Extracting Method-Object from Member Invocation

3.72/5 (10 votes)
12 Dec 2017CPOL3 min read 7.5K   15  
The article allows extracting java.lang.reflect.Method using anonymous class or lambda

Introduction

Every method in Java class corresponds to Method object, that can be retrieved in the runtime. This object has a plenty of useful features: it describes the properties of the method, can invoke method itself, retrieves method’s annotations etc.

To get Method object we need its name and parameter types.

Java
Class clazz = object.getClass();
String methodName = ...//The <code>String</code> name of the method
Class [] methodParams = new Class[] {/*The types of the parameters*/};
Method method = clazz.getDeclaredMethod(methodName, methodParams);

Unfortunately, the method does not relate to its String name, so if programmer changes the method name, the program fails in the runtime.

In .NET the excerption of MethodInfo (.NET name for java's Method) not via method’s name is quite simple [1]:

  • Create delegate with the same signature
  • Call the delegate’s GetMethodInfo()

 

To solve this problem in Java the recorder-technique [2] is used:

  • Create the proxy
  • Call the method of proxy
  • Record the method name and parameter types
  • Excerpt the Method object from the record’s data

The famous Mockito unit-testing [3] framework uses such technique.

 

There is almost impossible to proxy static and final methods using recorder-technique.

The unit-testing framework PowerMock [4] solves the problem of static and final methods via custom class loaders. However, PowerMock often conflicts with frameworks like Spring that use their own class loaders.

This article introduces another technique without any limitations on static and final methods and without any usage of custom class loaders.

Anonymous Classes (Old JDK)

If we have to use old JDK, then anonymous classes are the only option:

Java
Method method = new ReflectionUtils() {
    public void marker() {
        obj.testMethod(null);
    }
}.method();

The implementation of abstract marker() method plays the same role as in recorder-technique. The marker() method is never called, the compiled bytecode is inserted instead. Then the bytecode is analyzed using ASM and the appropriate Method is extracted.

 

For Method extraction the following steps should be performed:

  • Find marker method in compiled bytecode
  • Get bytecode of marker method
  • Find AbstractInsnNode.METHOD_INSN instruction in bytecode
  • Extract MethodInsnNode
  • Transform MethodInsnNode to Method

Then the different things could be done:

  • Build dynamic proxy around extracted Method
  • Transform it to MethodHandle
  • Bind Method's parameters to their predefined values etc.

 

In order to find marker method we are looking for the single abstract method in the ReflectUtils.getAbstractMethod():

Java
public static Method getAbstractMethod(Class<?> clazz) {
    List<method> methodList = new ArrayList<>();
    getAbstractMethods(clazz, methodList);
    if(methodList.size() == 0) return null;
    if(methodList.size() != 1) throw new ReflectUtilsException("The class have more then one abstract method");
    return methodList.get(0);
}

To extract appropriate Method from class we use AsmUtils.getMethod().
Firstly we need to obtain InputStream for given Class object:

Java
class AsmUtils {
...
    private static InputStream getClassStream(Class<?> clazz) throws IOException {
        String className = clazz.getName();
        String classResource = "/"+className.replaceAll("\\.", "/")+".class";
        return clazz.getResourceAsStream(classResource);
    }
...
}

Then, to extract Method from this InputStream some code manipulation with ASM framework should be done:

Java
class AsmUtils {
...
    public static java.lang.reflect.Method getMethod(Class<?> clazz, String methodName) {
        try {
            InputStream is = getClassStream(clazz);
            ClassReader cr = new ClassReader(is);
            ClassNode cn = new ClassNode();
            cr.accept(cn, 0);
            for(MethodNode mn: (List<methodnode>) cn.methods) {
                if(mn.name.equals(methodName)) {
                    return getMethodFromNode(mn);
                }
            }
            throw new ReflectionUtilsException("Class does not have 'consume' method");
            
        } catch (IOException e) {
            throw new ReflectionUtilsException(e);
        }
    }

...
}

Ultimately, the getMethodFromNode is used for extraction Method from MethodNode:

Java
private static java.lang.reflect.Method getMethodFromNode(MethodNode mn) {
    MethodInsnNode methNode = findMethod(mn);
    String name = methNode.name;
    Method method = new Method(name, methNode.desc);
    Class<?>[] classArray = getClassArray(method);

    String className = methNode.owner.replaceAll("/", ".");
    Class<?> clazz = getClass(className);

    try {
        java.lang.reflect.Method meth = clazz.getDeclaredMethod(name, classArray);
        meth.setAccessible(true);
        return meth;
    } catch (Exception e) {
        throw new ReflectionUtilsException(e);
    }
}

Additional details could be found in attached source code reflect-utils-jdk-6.zip

Extraction of Method Information from Lambda (New JDK)

Suppose we have a class TestClass with method testMethod

Java
class TestClass {
    public void testMethod(String name) {
        ...
    }
}

The goal is to define some API to get Method object related to testMethod

 

Let RS be the interface:

@FunctionalInterface
public interface RS extends Runnable, Serializable {
    default Method method() {
        return LambdaUtils.fromLambda(this);
    }
}

Interface RS allows to extract Method from appropriate lambda-object. The method is called inside lambda-object to record its name and parameter types:

Java
TestClass obj = new TestClass();
Method method = ((RS)()->{obj.testMethod(null);}).method();

The lambda is never called, it is used only for recording the method.
RS interface extends the marking interface Serializable and makes the lambda a serializable object.
Every serializable lambda has writeReplace method called by the java-runtime during serialization process.
As the result of writeReplace invocation, we get SerializedLambda object.

 

Below we call this method using reflection:

Java
public class SerializedLambdaHelper {
...
    public static SerializedLambda getSerializedLambda(Serializable lambda){
        Method method = ClassUtils.getMetod(lambda.getClass(), "writeReplace");
        return (SerializedLambda) ClassUtils.invokeMethod(lambda, method);
    }
}

For Method extraction from SerializedLambda the following steps should be performed:

  • Find the MethodNode containing this lambda
  • Find MethodInsnNode inside this MethodNode
  • Get the Method from MethodInsnNode
Java
public class SerializedLambdaHelper {
    ...
    public static Method getReflectedMethod(Serializable lambda) {
        SerializedLambda serializedLambda = getSerializedLambda(lambda);
        Class<?> containingClass = ClassUtils.getClassFromStr(serializedLambda.getImplClass());
        String methodName = serializedLambda.getImplMethodName();
        MethodNode methodNode = AsmUtils.findMethodNode(containingClass, methodName);
        MethodInsnNode methNode = AsmUtils.findMethod(methodNode, false);
        return AsmUtils.getMethodFromMethodNode(methNode);
    }
    ...
}

 

The low-level implementation details could be found in the class AsmUtils from attached code.

 

Running Examples

The Maven should be installed.
I tested the examples using JDK-8 and JDK-9. You could run examples using IDE of your choice, or from the command line. If you use IDE, just import Maven project.
Alternatively, you could run the project from command line. Just compile and test project using mvn test in the pom.xml directory.

References

  1. How to get the MethodInfo of a Java 8 method reference?
  2. Typesafe database interaction with Java 8
  3. Mockito framework
  4. PowerMock
  5. Can you inspect the byte code of a Java 8 lambda at runtime?
  6. Reflection type inference on Java 8 Lambdas

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)