【问题标题】:get vs getProperty in groovygroovy中的get vs getProperty
【发布时间】:2015-08-23 17:16:18
【问题描述】:

让我吃惊!

根据groovy的文档,groovy可以使用“getProperty”方法来获取对象的属性。所以当我想改变获取特殊对象属性的行为时,我使用一个类别类来覆盖“getProperty”方法。但是,它不起作用。 最后,我发现groovy框架使用类别类中的“get”方法来获取属性,即使对象不是地图。 我的问题是,它是一个 bug 还是 groovy 就这样工作。

这是类别类。

class DynaBeanExtension {

    public static void setProperty(DynaBean bean, String propertyName, def newValue) {
        try {
            PropertyUtilsBean pu = null;
            if (bean instanceof CustomWrapDynaBean) {
                pu = bean.propertyUtilsBean;
            }
            if (pu != null) {
                pu.setProperty(bean, propertyName, newValue);
            } else {
                PropertyUtils.setProperty(bean, propertyName, newValue);
            }
        } catch (IllegalArgumentException ex) {
            bean.propertyMissing(propertyName, newValue);
        }
    }

    public static def getProperty(DynaBean bean, String propertyName) {
        try {
            PropertyUtilsBean pu = null;
            if (bean instanceof CustomWrapDynaBean) {
                pu = bean.propertyUtilsBean;
            }
            if (pu != null) {
                return pu.getProperty(bean, propertyName);
            } else {
                return PropertyUtils.getProperty(bean, propertyName);
            }
        } catch (IllegalArgumentException ex) {
            return bean.propertyMissing(propertyName);
        }
    }

    public static def get(DynaBean bean, String propertyName) {
        try {
            PropertyUtilsBean pu = null;
            if (bean instanceof CustomWrapDynaBean) {
                pu = bean.propertyUtilsBean;
            }
            if (pu != null) {
                return pu.getProperty(bean, propertyName);
            } else {
                return PropertyUtils.getProperty(bean, propertyName);
            }
        } catch (IllegalArgumentException ex) {
            return bean.propertyMissing(propertyName);
        }
    }

这是测试代码:

public static class TestSubClass {

    private final int e = 3, f = 4;
    private final Map<String, Object> m = new HashMap<>();

    public int getE() {
        return e;
    }

    public int getF() {
        return f;
    }

    public Map<String, Object> getM() {
        return m;
    }

    @Override
    public String toString() {
        return "TestSubClass{" + "e=" + e + ", f=" + f + ", m=" + m + '}';
    }

}

public static class TestClass {

    private final int a = 1;
    private final TestSubClass b = new TestSubClass();

    public int getA() {
        return a;
    }

    public TestSubClass getB() {
        return b;
    }

    @Override
    public String toString() {
        return "TestClass{" + "a=" + a + ", b=" + b + '}';
    }

}

Map<String, String> pMap = new HashMap<>();
pMap.put("b.e", "c");
PropertyUtilsBean pu = new PropertyUtilsBean();
pu.setResolver(new ExResolver(pMap));
TestClass testObj = new TestClass();
DynaBean bean = new CustomWrapDynaBean(testObj, pu);

int c = use(DynaBeanExtension) {
    bean.c;
}

这是ExResolver的代码:

public class ExResolver implements Resolver {

    private static final char NESTED = '.';
    private static final char MAPPED_START = '(';
    private static final char MAPPED_END = ')';
    private static final char INDEXED_START = '[';
    private static final char INDEXED_END = ']';

    private final Resolver resolver;
    private final Map<String, String> pMap;

    public ExResolver(Map<String, String> pMap) {
        this(new DefaultResolver(), pMap);
    }

    public ExResolver(Resolver resolver, Map<String, String> pMap) {
        this.resolver = resolver;
        this.pMap = new HashMap<>(pMap);
    }

    private String resolveExpr(String expression) {
        for (Map.Entry<String, String> entry : pMap.entrySet()) {
            if (expression.startsWith(entry.getValue())) {
                String to = entry.getValue();
                if (expression.length() == entry.getValue().length()) {
                    return entry.getKey();
                } else {
                    int toTest = expression.codePointAt(to.length());
                    if (toTest == NESTED || toTest == MAPPED_START || toTest == INDEXED_START) {
                        return entry.getKey() + expression.substring(to.length(), expression.length());
                    } else {
                        return expression;
                    }
                }
            }
        }
        return expression;
    }

    @Override
    public int getIndex(String expression) {
        expression = resolveExpr(expression);
        return resolver.getIndex(expression);
    }

    @Override
    public String getKey(String expression) {
        expression = resolveExpr(expression);
        return resolver.getKey(expression);
    }

    @Override
    public String getProperty(String expression) {
        expression = resolveExpr(expression);
        return resolver.getProperty(expression);
    }

    @Override
    public boolean hasNested(String expression) {
        expression = resolveExpr(expression);
        return resolver.hasNested(expression);
    }

    @Override
    public boolean isIndexed(String expression) {
        expression = resolveExpr(expression);
        return resolver.isIndexed(expression);
    }

    @Override
    public boolean isMapped(String expression) {
        expression = resolveExpr(expression);
        return resolver.isMapped(expression);
    }

    @Override
    public String next(String expression) {
        expression = resolveExpr(expression);
        return resolver.next(expression);
    }

    @Override
    public String remove(String expression) {
        expression = resolveExpr(expression);
        return resolver.remove(expression);
    }

}

调用的是“get”,而不是“getProperty”

更重要的是,在真实情况下DynaBeanExtension是用groovy编译的。 bean的构建是用java编译的。然后使用binding,将其放入测试代码中,这是一个由java代码执行的运行时脚本。

【问题讨论】:

  • 你可以将代码添加到问题中并在那里描述什么不起作用
  • 好的。我把代码放到答案的响应中
  • 不,你没有。请edit 这个问题。 SO 不是论坛,而是一个 QA 站点。
  • 另外,请提供“groovy 文档”的链接。如果文档的措辞令人困惑或模棱两可,我们当然希望解决这个问题。
  • 我用WrapDynaBean 替换了CustomWrapDynaBean,正如你所建议的,但是我可以用什么替换ExResolver 以便重现你的结果?

标签: groovy


【解决方案1】:

这发生在编译本身。让我们看一个更简单的例子。

class Main {
    static void main(def args) {
        Foo foo = new Foo()
        foo.str = ""
        foo.str
    }
}

对于 Groovy 类

class Foo {
    String str
}

如果你反编译 Main 类,你会看到它是

public class Main implements GroovyObject {
    public Main() {
        Main this;
        CallSite[] arrayOfCallSite = $getCallSiteArray();
        MetaClass localMetaClass = $getStaticMetaClass();
        this.metaClass = localMetaClass;
    }

    public static void main(String... args) {
        CallSite[] arrayOfCallSite = $getCallSiteArray();
        Foo foo = (Foo)ScriptBytecodeAdapter.castToType(arrayOfCallSite[0].callConstructor(Foo.class), Foo.class);
        String str = "";
        ScriptBytecodeAdapter.setGroovyObjectProperty(str, Main.class, foo, (String)"str");

        arrayOfCallSite[1].callGroovyObjectGetProperty(foo);
    }
}

.[property] = 调用被编译为 ScriptBytecodeAdapter.setGroovyObjectProperty,然后又调用链 MetaClassImpl.setProperty > MetaMethod.doMethodInvoke > CachedMethod.invoke > java.lang.reflect.Method.invoke > [setter]

.[property] 调用被编译为arrayOfCallSite[1].callGroovyObjectGetProperty,进而调用链 AbstractCallSite.callGroovyObjectGetProperty > GetEffectivePogoPropertySite.getProperty > MethodMetaProperty$GetBeanMethodMetaProperty.getProperty > MetaMethod.doMethodInvoke > CachedMethod.invoke > java.lang.reflect.Method.invoke > [getter]

对于 Java 类

如果你使用被调用类的 Java 版本,像这样

public class Foo {
    private String str;

    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }
}

同样的Main反编译成

public class Main implements GroovyObject {
    public Main() {
        Main this;
        CallSite[] arrayOfCallSite = $getCallSiteArray();
        MetaClass localMetaClass = $getStaticMetaClass();
        this.metaClass = localMetaClass;
    }

    public static void main(String... args) {
        CallSite[] arrayOfCallSite = $getCallSiteArray();
        Foo foo = (Foo)ScriptBytecodeAdapter.castToType(arrayOfCallSite[0].callConstructor(Foo.class), Foo.class);
        String str = "";
        ScriptBytecodeAdapter.setProperty(str, null, foo, (String)"str");

        arrayOfCallSite[1].callGetProperty(foo);
    }
}

.[property] = 调用被编译为ScriptBytecodeAdapter.setProperty,进而调用链[Class].setProperty > InvokerHelper.setProperty -> MetaClassImpl.setProperty > MetaMethod.doMethodInvoke > MetaMethod.doMethodInvoke > CachedMethod.invoke > java.lang.reflect.Method.invoke > [setter]

.[property] 调用被编译为 arrayOfCallSite[1].callGroovyObjectGetProperty,然后调用链 AbstractCallSite.callGetProperty > GetEffectivePojoPropertySite.getProperty > MethodMetaProperty$GetBeanMethodMetaProperty.getProperty > MetaMethod.doMethodInvoke > CachedMethod.invoke > java.lang.reflect.Method.invoke > [getter]

更正您的代码

从这些调度链中可以看出,您已经正确地覆盖了 getter(因为它发生在类本身中),但是如果您想覆盖 getPropertysetProperty,则必须在 @ 中执行此操作987654364@,而不是班级本身。您看到的行为是预期的。这段代码演示了如何覆盖每个

class Foo {
    String bar
}

// override using setter in category
@Category(Foo)
class FooCategory {
    public String getBar() {
        println "in getter"
    }
    public void setBar(String bar) {
        println "in setter"
    }
}
use (FooCategory) {
    Foo foo = new Foo()
    foo.bar = ""
    foo.bar
}

// override using metaClass
Foo.metaClass.getProperty { String pname ->
    println "in getProperty"
}
Foo.metaClass.setProperty { String pname, Object pValue ->
    println "in setProperty"
}
Foo foo = new Foo()
foo.bar = ""
foo.bar

输出

in setter
in getter
in setProperty
in getProperty

而且因为getProperty/setProperty 调用会(最终)调度到 getter/setter,所以您可以完全阻止 getter/setter 被调用,就像这样

class Foo {
    String bar
}

Foo.metaClass.getProperty { String pname ->
    println "in getProperty"
}
Foo.metaClass.setProperty { String pname, Object pValue ->
    println "in setProperty"
}

@Category(Foo)
class FooCategory {
    String getBar() {
        println "in getter"
    }
    void setBar(String bar) {
        println "in setter"
    }
}

use (FooCategory) {
    Foo foo = new Foo()
    foo.bar = "hi foo1"
    foo.bar
}

输出

in setProperty
in getProperty

【讨论】:

  • 但是为什么要调用“get”呢?
  • 我试图对我之前所做的“setProperty 调用适当的设置器”评论给出更详细的解释。这有帮助吗?
  • 您的解释非常详尽。现在我知道为什么“getProperty”不起作用,以及如何使它正常工作。但我仍然不知道为什么“得到”工作。在 MetaClassImpl 的源代码中,我发现在搜索属性时,它选择“get”方法来完成任务,但这在文档中没有提到。这是标准行为吗?
  • 我想我比我想象的还要困。在这里,当您询问 getter 时,我详细解释了 setter。我已经更新了解决这两个问题的答案。
  • 但是您仍然没有回答为什么调用“get”。您刚刚告诉我为什么不调用“getProperty”。根据您的回答,我不能使用类别来覆盖获取属性的行为,而是使用元类。那么有没有标准的方法来使用类别来改变行为呢?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-06-21
  • 2010-12-30
  • 1970-01-01
  • 2014-08-13
  • 1970-01-01
  • 1970-01-01
  • 2019-09-25
相关资源
最近更新 更多