Groovy-ProxyMetaClass的拦截器不影响内部方法调用

时间:2018-11-17 16:57:11

标签: groovy interceptor metaclass

该代码段摘自《 Groovy in action 2nd》,并作了一些细微修改。

1此代码可以正常工作

<?php
// fix array creation, dont use `->` use `=>`
// and the keys cannot all be the same
$aa=array("a"=>1,"b"=>2,"c"=>3,"d"=>4);

$otherV = '';
foreach($aa as $key => $value){
   //concatenate the value into otherV using .=
   $otherV .= $value.",";
}
rtrim( $otherV, ',');  // remove last comma
echo $otherV;
?>

输出:

package test

class InspectMe {
    int outer(){
        return inner()
    }
    int inner(){
        return 1
    }
}

def tracer = new TracingInterceptor(writer: new StringWriter())
def proxyMetaClass = ProxyMetaClass.getInstance(InspectMe)
proxyMetaClass.interceptor = tracer
InspectMe inspectMe = new InspectMe()

inspectMe.metaClass = proxyMetaClass
inspectMe.outer()
println(tracer.writer.toString())

2,但是此代码的输出不同

before test.InspectMe.outer()
  before test.InspectMe.inner()
  after  test.InspectMe.inner()
after  test.InspectMe.outer()

输出:

package test

class InspectMe {
    int outer(){
        return inner()
    }
    int inner(){
        return 1
    }
}

def tracer = new TracingInterceptor(writer: new StringWriter())
def proxyMetaClass = ProxyMetaClass.getInstance(InspectMe)
proxyMetaClass.interceptor = tracer
InspectMe inspectMe = new InspectMe()

proxyMetaClass.use(inspectMe){
    inspectMe.outer()
}
println(tracer.writer.toString())

在第二个代码中,TracingInterceptor似乎没有拦截内部方法。 也许这是正常行为,但是在我看来,这就像一个错误。 有人可以解释一下吗?

1 个答案:

答案 0 :(得分:2)

我不知道这是否是错误,但是我可以解释为什么会发生这种不同的行为。让我们从字节码级别分析InspectMe.outer()方法实现的样子开始(我们反编译.class文件):

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import org.codehaus.groovy.runtime.BytecodeInterface8;
import org.codehaus.groovy.runtime.callsite.CallSite;
import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;

public class InspectMe implements GroovyObject {
    public InspectMe() {
        CallSite[] var1 = $getCallSiteArray();
        MetaClass var2 = this.$getStaticMetaClass();
        this.metaClass = var2;
    }

    public int outer() {
        CallSite[] var1 = $getCallSiteArray();
        return !__$stMC && !BytecodeInterface8.disabledStandardMetaClass() ? this.inner() : DefaultTypeTransformation.intUnbox(var1[0].callCurrent(this));
    }

    public int inner() {
        CallSite[] var1 = $getCallSiteArray();
        return 1;
    }
}

如您所见,outer()方法测试以下谓词

!__$stMC && !BytecodeInterface8.disabledStandardMetaClass()

,如果其求值为true,它将直接调用this.inner()方法,从而避免Groovy的MOP(元对象协议)层(在这种情况下不涉及元类)。否则,它将调用var1[0].callCurrent(this),这意味着inner()方法是通过Groovy的MOP调用的,该方法具有执行过程中涉及的元类和拦截器。

问题中显示的两个示例提出了一种不同的设置元类字段的方法。在第一种情况下:

def tracer = new TracingInterceptor(writer: new StringWriter())
def proxyMetaClass = ProxyMetaClass.getInstance(InspectMe)
proxyMetaClass.interceptor = tracer
InspectMe inspectMe = new InspectMe()

inspectMe.metaClass = proxyMetaClass // <-- setting metaClass with DefaultGroovyMethods
inspectMe.outer()

println(tracer.writer.toString())

我们正在使用Groovy的MOP层调用inspectMe.setMetaClass(proxyMetaClass)方法。 DefaultGroovyMethods.setMetaClass(GroovyObject self, MetaClass metaClass)将此方法添加到InspectMe类中。

现在,如果我们快速看一下setMetaClass方法的实现方式,我们会发现一些有趣的东西:

/**
 * Set the metaclass for a GroovyObject.
 * @param self the object whose metaclass we want to set
 * @param metaClass the new metaclass value
 * @since 2.0.0
 */
public static void setMetaClass(GroovyObject self, MetaClass metaClass) {
    // this method was introduced as to prevent from a stack overflow, described in GROOVY-5285
    if (metaClass instanceof HandleMetaClass)
        metaClass = ((HandleMetaClass)metaClass).getAdaptee();

    self.setMetaClass(metaClass);
    disablePrimitiveOptimization(self);
}

private static void disablePrimitiveOptimization(Object self) {
    Field sdyn;
    Class c = self.getClass();
    try {
        sdyn = c.getDeclaredField(Verifier.STATIC_METACLASS_BOOL);
        sdyn.setBoolean(null, true);
    } catch (Throwable e) {
        //DO NOTHING
    }
}

它在最后调用私有方法disablePrimitiveOptimization(self)。此方法负责将true分配给__$stMC类字段(常量Verifier.STATIC_METACLASS_BOOL存储__$stMC值)。在我们的情况下是什么意思?这意味着outer()方法中的谓词:

return !__$stMC && !BytecodeInterface8.disabledStandardMetaClass() ? this.inner() : DefaultTypeTransformation.intUnbox(var1[0].callCurrent(this));

评估为false,因为__$stMC设置为true。在这种情况下,inner()方法通过使用metaClass和拦截器的MOP执行。

好的,但是它说明了第一种可以正常工作的情况。在第二种情况下会发生什么?

def tracer = new TracingInterceptor(writer: new StringWriter())
def proxyMetaClass = ProxyMetaClass.getInstance(InspectMe)
proxyMetaClass.interceptor = tracer
InspectMe inspectMe = new InspectMe()

proxyMetaClass.use(inspectMe){
    inspectMe.outer()
}

println(tracer.writer.toString())

首先,我们需要检查proxyMetaClass.use()是什么样的:

/**
 * Use the ProxyMetaClass for the given Closure.
 * Cares for balanced setting/unsetting ProxyMetaClass.
 *
 * @param closure piece of code to be executed with ProxyMetaClass
 */
public Object use(GroovyObject object, Closure closure) {
    // grab existing meta (usually adaptee but we may have nested use calls)
    MetaClass origMetaClass = object.getMetaClass();
    object.setMetaClass(this);
    try {
        return closure.call();
    } finally {
        object.setMetaClass(origMetaClass);
    }
}

这非常简单-在闭包执行时替换metaClass,并在闭包执行完成时将旧的metaClass放回原处。听起来像第一种情况,对吗?不必要。这是Java代码,它直接调用object.setMetaClass(this)方法(object类型为GroovyObject的变量,其中包含setMetaClass方法)。这意味着字段__$stMC未设置为true(默认值为false),因此outer()方法中的谓词必须求值:

BytecodeInterface8.disabledStandardMetaClass()

如果运行第二个示例,我们将看到此方法调用返回false

enter image description here

这就是为什么整个表达式

!__$stMC && !BytecodeInterface8.disabledStandardMetaClass()

求值为true,直接执行调用this.inner()的分支。

结论

我不知道它是否是有意的,但是如您所见,动态setMetaClass方法禁用原始优化并继续使用MOP,而ProxyMetaClass.use()设置metaClass保持启用和导致原始优化直接方法调用。我想这个示例说明了在实现ProxyMetaClass类时没有人想到的一个极端情况。

更新

这两种方法之间似乎存在差异,因为ProxyMetaClass.use()对于Groovy 1.x是implemented in 2005,并且是上一次in 2009进行了更新。 __$stMC字段是在in 2011处添加的,DefaultGroovyMethods.setMetaClass(GroovyObject object, Closure cl)是根据其javadoc于2012年引入的,该文档指出此方法自Groovy 2.0起可用。