get vs getProperty in groovy

拜拜、爱过 提交于 2020-01-14 10:33:31

问题


It surprise me!

According to the document of groovy, groovy may use "getProperty" method to get the property of a object. So when I want to change the behavier of getting property on the special object, I use a category class to override the "getProperty" method. However, it does not work. At last, I found groovy framework use the "get" method in the category class to get property, even if the object is not a map. My question is that is it a bug or groovy just work like that.

This is the category class.

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);
        }
    }

This is the test code:

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;
}

This is the code of 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" is invoked, not "getProperty"

What's more, in the real situation DynaBeanExtension is compiled with groovy. The construction of bean is compiled with java. Then by using binding, I put it into the test code which is a runtime script executed by java code.


回答1:


This happens in the compilation itself. Let's look at a simpler example.

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

For Groovy classes

class Foo {
    String str
}

If you decompile the Main class, you'll see it is

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);
    }
}

A .[property] = call gets compiled to a ScriptBytecodeAdapter.setGroovyObjectProperty, that in turn calls the chain MetaClassImpl.setProperty > MetaMethod.doMethodInvoke > CachedMethod.invoke > java.lang.reflect.Method.invoke > [setter]

And a .[property] call gets compiled to a arrayOfCallSite[1].callGroovyObjectGetProperty, that in turn calls the chain AbstractCallSite.callGroovyObjectGetProperty > GetEffectivePogoPropertySite.getProperty > MethodMetaProperty$GetBeanMethodMetaProperty.getProperty > MetaMethod.doMethodInvoke > CachedMethod.invoke > java.lang.reflect.Method.invoke > [getter]

For Java classes

If you use a Java version of the class being called, like this

public class Foo {
    private String str;

    public String getStr() {
        return str;
    }

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

The same Main decompiles to

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);
    }
}

A .[property] = call gets compiled to a ScriptBytecodeAdapter.setProperty, that in turn calls the chain [Class].setProperty > InvokerHelper.setProperty -> MetaClassImpl.setProperty > MetaMethod.doMethodInvoke > CachedMethod.invoke > java.lang.reflect.Method.invoke > [setter]

And a .[property] call gets compiled to a arrayOfCallSite[1].callGroovyObjectGetProperty, that in turn calls the chain AbstractCallSite.callGetProperty > GetEffectivePojoPropertySite.getProperty > MethodMetaProperty$GetBeanMethodMetaProperty.getProperty > MetaMethod.doMethodInvoke > CachedMethod.invoke > java.lang.reflect.Method.invoke > [getter]

To correct your code

As you can see from these dispatch chains, you've overridden the getter correctly (since it happens in the class itself), but if you want to override getProperty or setProperty, you have to do this in metaClass, and not the class itself. The behavior you're seeing is expected. This code demonstrates how to override each

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

outputs

in setter
in getter
in setProperty
in getProperty

And because the getProperty/setProperty call makes the dispatch (eventually) to the getter/setter, you can prevent the getter/setter from being called at all, like this

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
}

outputs

in setProperty
in getProperty


来源:https://stackoverflow.com/questions/30722700/get-vs-getproperty-in-groovy

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!