Groovy - перехватчик ProxyMetaClass не влияет на вызовы внутренних методов - PullRequest
0 голосов
/ 17 ноября 2018

Фрагмент кода из книги , с небольшими изменениями.

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 не перехватывает внутренние методы во втором коде.Может быть, это нормальное поведение, но мне кажется, что это ошибка.Может кто-нибудь, пожалуйста, объясните это?

1 Ответ

0 голосов
/ 18 ноября 2018

Я не знаю, является ли это ошибкой или нет, но я могу объяснить, почему происходит такое другое поведение.Давайте начнем с анализа того, как реализация метода 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:

enter image description here

И именно поэтому все выражение

!__$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.

...