Я не знаю, является ли это ошибкой или нет, но я могу объяснить, почему происходит такое другое поведение.Давайте начнем с анализа того, как реализация метода 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()
вызывается через MOP Groovy с метаклассом и перехватчиком, участвующим в его выполнении.
Два примера, которые вы показали в этом вопросе, представляют другой способ установки метаклассаполе.В первом случае:
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())
мы вызываем метод inspectMe.setMetaClass(proxyMetaClass)
, используя слой MOP Groovy.Этот метод добавляется в класс InspectMe
на DefaultGroovyMethods.setMetaClass(GroovyObject self, MetaClass metaClass)
.
Теперь, если мы кратко рассмотрим, как реализован этот метод 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()
выполняется через 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()
Если мы запустимВо втором примере мы увидим, что этот вызов метода возвращает false
:
И именно поэтому все выражение
!__$stMC && !BytecodeInterface8.disabledStandardMetaClass()
оценивается как true
, и ветвь, которая вызывает this.inner()
, непосредственно выполняется.
Заключение
Я не знаю, было ли это предназначено или нет, но, как вы можете видеть, динамический метод setMetaClass
отключает примитивную оптимизацию и продолжает использовать MOP, в то время как ProxyMetaClass.use()
устанавливает метакласссохранение примитивных оптимизаций включено и вызывало прямой вызов метода.Я предполагаю, что этот пример показывает угловой случай, о котором никто не задумывался при реализации ProxyMetaClass
class.
UPDATE
Кажется, что разница между этими двумя методами существует, потому что ProxyMetaClass.use()
было реализовано в 2005 для Groovy 1.x и обновлено в последний раз в 2009 .Это поле __$stMC
было добавлено в 2011 году , а DefaultGroovyMethods.setMetaClass(GroovyObject object, Closure cl)
было введено в 2012 году в соответствии с его javadoc, в котором говорится, что этот метод доступен начиная с Groovy 2.0.