2021SC@SDUSC
Method call implementation in Groovy
Previously, we analyzed the biggest extension of groovy to Java, that is, metaprogramming. Through the introduction of several core classes in groovy metaprogramming and the analysis of source code, we clearly understand how the bottom layer of groovy implements metaprogramming. Groovy metaprogramming has many uses, such as dynamically creating methods and dynamically intercepting methods through metaprogramming, Method synthesis and so on. If you are interested, you can learn more by yourself. This time, let's analyze the call implementation of methods in groovy.
People familiar with Groovy syntax should know that the values of two variables can be easily exchanged in Groovy, such as:
def (a, b) = [1, 2]; (a, b) = [b, a];
In this way, the values of a and B variables are exchanged. Is it convenient? How is Groovy implemented?
Let's first look at the generated bytecode file. The key codes are as follows:
// Method descriptor #39 ()Ljava/lang/Object; // Stack: 4, Locals: 6 public java.lang.Object run(); invokestatic Main.$getCallSiteArray() : org.codehaus.groovy.runtime.callsite.CallSite[] [17] astore_1 iconst_2 anewarray java.lang.Object [41] dup iconst_0 iconst_1 invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [47] aastore dup iconst_1 iconst_2 invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [47] aastore invokestatic org.codehaus.groovy.runtime.ScriptBytecodeAdapter.createList(java.lang.Object[]) : java.util.List [53] astore_2 aload_1 ldc <Integer 1> [54] aaload aload_2 iconst_0 invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [47] invokeinterface org.codehaus.groovy.runtime.callsite.CallSite.call(java.lang.Object, java.lang.Object) : java.lang.Object [57] [nargs: 3] astore_3 [a] aload_1 ldc <Integer 2> [58] aaload aload_2 iconst_1 invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [47] invokeinterface org.codehaus.groovy.runtime.callsite.CallSite.call(java.lang.Object, java.lang.Object) : java.lang.Object [57] [nargs: 3] astore 4 [b] aload_2 pop iconst_2 anewarray java.lang.Object [41] dup iconst_0 aload 4 [b] aastore dup iconst_1 aload_3 [a] aastore invokestatic org.codehaus.groovy.runtime.ScriptBytecodeAdapter.createList(java.lang.Object[]) : java.util.List [53] astore 5 aload_1 ldc <Integer 3> [59] aaload aload 5 iconst_0 invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [47] invokeinterface org.codehaus.groovy.runtime.callsite.CallSite.call(java.lang.Object, java.lang.Object) : java.lang.Object [57] [nargs: 3] astore_3 [a] aload_1 ldc <Integer 4> [60] aaload aload 5 iconst_1 invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [47] invokeinterface org.codehaus.groovy.runtime.callsite.CallSite.call(java.lang.Object, java.lang.Object) : java.lang.Object [57] [nargs: 3] astore 4 [b] aload 5 areturn aconst_null areturn
We decompile it, similar to the following code:
public Object main(){ org.codehaus.groovy.runtime.callsite.CallSite[] callsite = Main.$getCallSiteArray(); List alist = org.codehaus.groovy.runtime.ScriptBytecodeAdapter.createList(new Object[]{1,2}); Object a = callsite[1].call(alist, 0);//It is equivalent to alist.getAt(0) and alist.get(0); Object b = callsite[2].call(alist, 1);//Equivalent to alist.getAt(1) equivalent to alist.get(1); List blist = org.codehaus.groovy.runtime.ScriptBytecodeAdapter.createList(new Object[]{b,a}); a = callsite[3].call(blist, 0);//Equivalent to blist.getAt(0) equivalent to blist.get(0); b = callsite[4].call(blist, 1);//Equivalent to blist.getAt(1) equivalent to blist.get(1); } private static synthetic SoftReference<CallSiteArray> $callSiteArray; private static synthetic org.codehaus.groovy.runtime.callsite.CallSite[] $getCallSiteArray(){ org.codehaus.groovy.runtime.callsite.CallSiteArray rtrun = null; if(Main.$callSiteArray == null){ rtrun = Main.$createCallSiteArray(); Main.$callSiteArray = new SoftReference<CallSiteArray>(temp); }else{ rtrun = Main.$callSiteArray.get(); } return rturn.array; } private static synthetic org.codehaus.groovy.runtime.callsite.CallSiteArray $createCallSiteArray(){ String[] sarry = new String[5]; Main.$createCallSiteArray_1(sarry) return new CallSiteArray(Main.class, sarry); } private static synthetic void $createCallSiteArray_1(java.lang.String[] sarry){ sarry[0] = "runScript"; sarry[1] = "getAt"; sarry[2] = "getAt"; sarry[3] = "getAt"; sarry[4] = "getAt"; }
You can clearly see what the Groovy compiler does.
Although it is very simple, it can be seen that the compiler will create the corresponding CallSiteArray object according to the method call according to the execution mode of groovy. Groovy changes the call of many methods to CallSite.call, which supports many dynamic features. (groovy caches the created CallSite object, which is of SoftReference type.)
For example, in the above example, Groovy creates a CallSite and then calls the getAt method through CallSite.
After Groovy delegates the call to CallSite, at the beginning, the specific implementation of CallSite is CallSiteArray,
CallSiteArray delegates the call to the org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSite, Object, Object []) method
This method is responsible for creating a specific call point object and executing the corresponding method.
The final call point object created here is Org.codehaus.groovy.runtime.callsite.pojometamethodsite.pojometamethodsitenounwrappnocoerce. The specific code is as follows:
public static class PojoMetaMethodSiteNoUnwrapNoCoerce extends PojoMetaMethodSite { public PojoMetaMethodSiteNoUnwrapNoCoerce(CallSite site, MetaClassImpl metaClass, MetaMethod metaMethod, Class params[]) { super(site, metaClass, metaMethod, params); } public final Object invoke(Object receiver, Object[] args) throws Throwable { try { return metaMethod.invoke(receiver, args); } catch (GroovyRuntimeException gre) { throw ScriptBytecodeAdapter.unwrap(gre); } } }
The actual call will be delegated to the invoke method of this class.
The specific types of the methodmethod here are:
org.codehaus.groovy.runtime.dgm$243@527e5409[name: getAt params: [int] returns: class java.lang.Object owner: interface java.util.List]
This class has no source code, only class files. The decompilation is as follows:
import java.util.List; import org.codehaus.groovy.reflection.CachedClass; import org.codehaus.groovy.reflection.GeneratedMetaMethod; import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; public class dgm$243 extends GeneratedMetaMethod { public dgm$243(String paramString, CachedClass paramCachedClass, Class paramClass, Class[] paramArrayOfClass) { super(paramString, paramCachedClass, paramClass, paramArrayOfClass); } public Object invoke(Object paramObject, Object[] paramArrayOfObject) { return DefaultGroovyMethods .getAt((List) paramObject, DefaultTypeTransformation.intUnbox(paramArrayOfObject[0])); } public final Object doMethodInvoke(Object paramObject, Object[] paramArrayOfObject) { paramArrayOfObject = coerceArgumentsToClasses(paramArrayOfObject); return DefaultGroovyMethods .getAt((List) paramObject, DefaultTypeTransformation.intUnbox(paramArrayOfObject[0])); } }
You can see that the specific call is org. Codehaus. Groovy. Runtime. Defaultgroovymethods. Getat (list < T >, int)
/** * Support the subscript operator for a List. * <pre class="groovyTestCase">def list = [2, "a", 5.3] * assert list[1] == "a"</pre> * * @param self a List * @param idx an index * @return the value at the given index * @since 1.0 */ public static <T> T getAt(List<T> self, int idx) { int size = self.size(); int i = normaliseIndex(idx, size); if (i < size) { return self.get(i); } else { return null; } }
Finally, the call of a method is completed,.
Compared with Java, although it provides high flexibility, it also sacrifices some performance.