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 2011 而 DefaultGroovyMethods.setMetaClass(GroovyObject object, Closure cl)
是在 2012 年引入的,根据其 javadoc 说此方法可用,因为 Groovy 2.0.
代码片段来自
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 2011 而 DefaultGroovyMethods.setMetaClass(GroovyObject object, Closure cl)
是在 2012 年引入的,根据其 javadoc 说此方法可用,因为 Groovy 2.0.