java source code analysis - reflection Method class

java source code analysis - reflection Method class

1. What is it

The Method class provides information and access rights about a single Method on a class or interface. The Method reflected in the Method can be a strength Method on a class, a static Method, or an abstract Method on an interface.

public final class Method extends Executable

From the class diagram, you can see that the Executable class is the parent class of Mehod. In fact, it is the common base class of Method and Constructor.

2. If obtained

There are the following centralized methods to get the Method class:

2.1 methods for obtaining all

All methods defined by a Class can be obtained through the getDeclaredMethods method of the Class.

public Method[] getDeclaredMethods() throws SecurityException

2.2 access to public information

All public modified public methods can be obtained through the getMethods method of Class.

public Method[] getMethods() throws SecurityException		

2.3 obtaining the specified method

You can get the specified method of a Class by passing in the method name and parameter list type through the getMethod method of Class.

public Method getMethod(String name, Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException

Test:

Create an entity class:

package test.java.lang.reflect;

/**
 * @author sj
 * @title: MethodDemo
 * @description: TODO
 * @date 2021/1/1820:30
 */
public class MethodDemo {

    private Integer id;

    private String name;

    public MethodDemo() {
    }

    public MethodDemo(Integer id, String name) {
        this.id = id;
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static void test(String str){
        System.out.println(str);
    }

    private static String getStr(){
        return "hello getStr!";
    }

}

To create a test class:

package test.java.lang.reflect;

import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * @author sj
 * @title: MethodTest
 * @description: TODO
 * @date 2021/1/1820:32
 */
public class MethodTest {

    public static void main(String[] args) {
        try {
            Class clazz = Class.forName("test.java.lang.reflect.MethodDemo");
            //Get all
            Method[] methods =  clazz.getDeclaredMethods();
            System.out.println(Arrays.toString(methods));
            //Get public method
            Method[] methods1 = clazz.getMethods();
            System.out.println(Arrays.toString(methods1));
            //Get Method according to Method name and parameter list type
            Method setNameMethod = clazz.getMethod("setName", String.class);
            System.out.println(setNameMethod);
        } catch (ClassNotFoundException | NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

result:

3. Common methods

3.1getParameterTypes()

Used to get all parameter list types of the method.

//Get all parameter list types
public Class<?>[] getParameterTypes()

Test:

//Get Method according to Method name and parameter list type
Method setNameMethod = clazz.getMethod("setName", String.class);
//Get parameter list type
Class<?>[] parameterTypes = setNameMethod.getParameterTypes();
System.out.println(Arrays.toString(parameterTypes));

result:

3.2

4. Key analysis

Invoke method

public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException

This Method is to call the underlying Method of the Method object, which is equivalent to calling the actual Method of the declared class. For example:

Method setNameMethod = clazz.getMethod("setName", String.class);
MethodDemo methodDemo = new MethodDemo();
setNameMethod.invoke(methodDemo, "Zhang San");
System.out.println(methodDemo.getName());

result:

As we can see above, when we call method through reflection The invoke () method is actually similar to calling the setName() method of the MethodDemo class itself. How is it realized at the bottom? Let's look at the source code:

Through the source code, we roughly divide the implementation of the invoke method into three steps:

(1) Verification authority; (I won't take a closer look here. In fact, it is to verify the access rights.)

(2) Get method accessor;

(3) Execute the invoke method through the MethodAccessor.

MethodAccessor method accessor

Before parsing step 2), let's take a look at what a MethodAccessor is?

public interface MethodAccessor {
    Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException;
}

We can see that MethodAccessor is an interface with only one invoke method. Its implementation classes are as follows:

We found that it has three implementation classes (actually two specific implementation classes):

MethodAccessorImpl: is an abstract class.

NativeMethodAccessorImpl: the bottom layer calls the local method to implement invoke0, as shown in the following figure.

DelegatingMethodAccessorImpl: proxy class. In fact, the NativeMethodAccessorImpl class is also called to implement the invoke method (numinvocations < 15, which will be explained later).

Get method accessor

Let's take a look at how to get the MethodAccessor.

private MethodAccessor acquireMethodAccessor() {
        // First check to see if one has been created yet, and take it
        // if so
        MethodAccessor tmp = null;
        if (root != null) tmp = root.getMethodAccessor();
        if (tmp != null) {
            methodAccessor = tmp;
        } else {
            // Otherwise fabricate one and propagate it up to the root
            tmp = reflectionFactory.newMethodAccessor(this);
            setMethodAccessor(tmp);
        }

        return tmp;
    }

In the source code, root means that each Method object contains a root object, and there is a MethodAccessor object in this root object. If we obtain a Method object, it is equivalent to obtaining a mirror image of a root object. All such methods share the MethodAccessor object in root.

Therefore, we will first judge whether there is a MethodAccessor in the root. If so, we will use it directly. If not, we will create a MethodAccessor through the ReflectionFactory factory.

public MethodAccessor newMethodAccessor(Method var1) {
        checkInitted();//1) Verify initialization
        if (noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) {//2) Generate the corresponding MethodAccessor implementation class according to the conditions
            return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());
        } else {
            NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1);
            DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);
            var2.setParent(var3);
            return var3;
        }
    }

You can see that the source code of the newMethodAccessor() method is mainly divided into two parts:

1) Verify initialization

private static void checkInitted() {
        if (!initted) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    if (System.out == null) {
                        return null;
                    } else {
                        String var1 = System.getProperty("sun.reflect.noInflation");
                        if (var1 != null && var1.equals("true")) {
                            ReflectionFactory.noInflation = true;
                        }

                        var1 = System.getProperty("sun.reflect.inflationThreshold");
                        if (var1 != null) {
                            try {
                                ReflectionFactory.inflationThreshold = Integer.parseInt(var1);
                            } catch (NumberFormatException var3) {
                                throw new RuntimeException("Unable to parse property sun.reflect.inflationThreshold", var3);
                            }
                        }

                        ReflectionFactory.initted = true;
                        return null;
                    }
                }
            });
        }
    }

From the above, we can see that the initialization here is mainly to verify the reflectionfactory Whether the value of inflationthreshold is initialized and reflectionfactory Whether noinflation is set to true.

Both values are obtained from the system configuration file. If it is not obtained, there will also be a default value:

private static int inflationThreshold = 15;

So what are these two values for? Let's bury a foreshadowing here for the time being and explain later.

Let's go back to part 2) of the newMethodAccessor() method.

2) Generate the corresponding MethodAccessor implementation class according to the conditions

First, we will judge whether noifflation is true and is not an anonymous inner class. If the conditions are met, call new methodaccessorgenerator() The generatemethod method returns a MethodAccessor.

Otherwise, the DelegatingMethodAccessorImpl implementation class will be returned, which is actually the proxy of NativeMethodAccessorImpl.

Let's take a look at new methodaccessorgenerator() What does generatemethod do:

...
public MethodAccessor generateMethod(Class<?> var1, String var2, Class<?>[] var3, Class<?> var4, Class<?>[] var5, int var6) {
	return (MethodAccessor)this.generate(var1, var2, var3, var4, var5, var6, false, false, (Class)null);
}

...
private MagicAccessorImpl generate(final Class<?> var1, String var2, Class<?>[] var3, Class<?> var4, Class<?>[] var5, int var6, boolean var7, boolean var8, Class<?> var9) {
        ByteVector var10 = ByteVectorFactory.create();
        this.asm = new ClassFileAssembler(var10);
        this.declaringClass = var1;
        this.parameterTypes = var3;
        this.returnType = var4;
        this.modifiers = var6;
        this.isConstructor = var7;
        this.forSerialization = var8;
        this.asm.emitMagicAndVersion();
        short var11 = 42;
      
    ...//The code is a little singing, which is omitted here. If you are interested, you can see the source code

        if (this.asm.cpi() != var11) {
            throw new InternalError("Adjust this code (cpi = " + this.asm.cpi() + ", numCPEntries = " + var11 + ")");
        } else {
            this.asm.emitShort((short)1);
            this.asm.emitShort(this.thisClass);
            this.asm.emitShort(this.superClass);
            this.asm.emitShort((short)0);
            this.asm.emitShort((short)0);
            this.asm.emitShort((short)2);
            this.emitConstructor();
            this.emitInvoke();
            this.asm.emitShort((short)0);
            var10.trim();
            final byte[] var17 = var10.getData();
            return (MagicAccessorImpl)AccessController.doPrivileged(new PrivilegedAction<MagicAccessorImpl>() {
                public MagicAccessorImpl run() {
                    try {
                        return (MagicAccessorImpl)ClassDefiner.defineClass(var13, var17, 0, var17.length, var1.getClassLoader()).newInstance();	//Here is the key point. Construct a MagicAccessorImpl instance object
                    } catch (IllegalAccessException | InstantiationException var2) {
                        throw new InternalError(var2);
                    }
                }
            });
        }
    }

By looking at the source code of this method, we can see that in the end, we call the newInstance() method to generate a MagicAccessorImpl object, which contains the implementation of the method invoke() we need to call at the beginning. In other words, it is equivalent to building an object with an invoke() method. Why do you do this.

In fact, there are two versions of MethodAccessor implementation. One is implemented in Java, which is the above-mentioned MagicAccessorImpl, and the other is implemented through the local method native (c/c + + language).

The version implemented in Java needs more time during initialization, and the class loading requires both time and memory, but the performance is good in the long run;

The native version is just the opposite. It starts relatively fast, but after a long running time, it is no faster than the Java version. This is the performance characteristic brought by the optimization method of HotSpot, and it is also the common point of many virtual machines: crossing the native boundary, that is, crossing from Java program to c/c + + program will hinder the optimization. It is like a black box, which makes it difficult for virtual machines to analyze and inline. After a long running time, the managed version of code is faster.

In order to balance the performance of the two versions, Sun's JDK uses the "inflation" technique: let the Java method use the native version for the first few times when it is called by reflection. When the number of reflection calls exceeds the threshold, a special MethodAccessor implementation class will be generated, and the bytecode of the invoke() method will be generated. In the future, the Java version will be used for reflection calls to the Java method.

Sun's JDK has adopted this optimization since the 1.4 series.

PS. you can add - dsun. To the startup command reflect. If noInflation = true, the noInflation attribute of RefactionFactory will become true. In this way, the program will use the java version of MethodAccessor at the beginning without waiting for 15 calls.

Seeing here, do we remember the foreshadowing buried in front of us. Yes, the previously mentioned reflectionfactory Inflationthreshold and reflectionfactory Noifflation is a variable in the system, reflectionfactory Noinflation acts like a launcher. If it is true, the method accessor object of java version will be directly used at the bottom of the invoke method; Otherwise, the NativeMethodAccessorImpl implementation class representing the local method will be returned.

ReflectionFactory.inflationThreshold is equivalent to a threshold. When the threshold is exceeded, the java version of MethodAccessor object will also be used;

Execute invoke method through MethodAccessor

Next, let's look at the last part of the invoke method. Call MethodAccessor to execute the invoke method.

public interface MethodAccessor {
    Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException;
}

As mentioned above, MethodAccessor is an interface, which is implemented in different implementation classes. Let's take a look at the NativeMethodAccessorImpl class:

 public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
        if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
            MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
            this.parent.setDelegate(var3);
        }

        return invoke0(this.method, var1, var2);
    }

private static native Object invoke0(Method var0, Object var1, Object[] var2);

See the noInflation mechanism again. Here is to judge whether the value of numInvocations is greater than 15 by default. If not, directly call the native local method invoke0 to finally execute the call.

If the noInflation exceeds 15, a java version of MethodAccessor will be implemented as mentioned above, and finally its Java method will be called to execute the call.

5. Summary

1) The Method class is used to encapsulate the relevant information of the methods in the java class. It is an abstraction of all java methods;

2) The Method object can be obtained through the Class object;

3) The Method object provides many methods to obtain information about a Method of a class.

4)Method. The underlying layer of the invoke () method itself is not implemented by the method class, but delegated to the MethodAccessor and its implementation class; At the same time, the noInflation mechanism is adopted. By default, the first 15 times are executed by calling the local method invoke0. After 15 times, a java object will be created to execute the method.

Keywords: JDK

Added by onicsoft on Wed, 02 Feb 2022 19:13:03 +0200