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

Groovy - interceptor of ProxyMetaClass does not affect inner methods calls

代码片段来自一书,稍作修改。

1 此代码按预期工作

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())

输出:

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

2 但此代码的输出不同

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())

输出:

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

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

我不知道这是否是错误,但我可以解释为什么会出现这种不同的行为。我们先从字节码层面分析一下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(元对象协议)层(不涉及元 class在这种情况下)。否则,它会调用 var1[0].callCurrent(this),这意味着 inner() 方法通过 Groovy 的 MOP 调用,其中元 class 和拦截器参与其执行。

您在问题中显示的两个示例提供了设置 metaclass 字段的不同方式。第一种情况:

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 class。

现在,如果我们快速看一下这个 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 class 字段(常量 Verifier.STATIC_METACLASS_BOOL 存储 __$stMC 值)。这对我们来说意味着什么?表示outer()方法中的谓词:

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

计算为 false,因为 __$stMC 设置为 true。在这种情况下,inner() 方法通过带有元类和拦截器的 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);
    }
}

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

BytecodeInterface8.disabledStandardMetaClass()

如果我们运行第二个例子我们会看到这个方法调用returnsfalse:

这就是为什么整个表达式

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

评估为 true 并直接执行调用 this.inner() 的分支。

结论

我不知道它是否有意,但正如您所见,动态 setMetaClass 方法禁用原始优化并继续使用 MOP,而 ProxyMetaClass.use() 设置元类保持原始优化启用并导致直接方法调用。我想这个例子展示了一个在实施 ProxyMetaClass class.

时没有人想到的极端情况

更新

这两种方法之间似乎存在差异,因为 ProxyMetaClass.use()implemented in 2005 for Groovy 1.x and it got updated for the last time in 2009. This __$stMC field was added in 2011DefaultGroovyMethods.setMetaClass(GroovyObject object, Closure cl) 是在 2012 年引入的,根据其 javadoc 说此方法可用,因为 Groovy 2.0.