groovy

"object is not an instance of declaring class" thrown invoking a method on Groovy class, wrapped in a Spring proxy

Details

  • Type: Bug Bug
  • Status: Closed Closed
  • Priority: Major Major
  • Resolution: Fixed
  • Affects Version/s: 1.5, 1.5.2, 1.5.4
  • Fix Version/s: 1.6-rc-1, 1.5.8, 1.7-beta-1
  • Component/s: None
  • Labels:
    None
  • Environment:
    Groovy 1.5.4
    Spring 2.5
  • Testcase included:
    yes
  • Number of attachments :
    1

Description

I suspect this is connected with GROOVY-2006, here is the case:
I have two Spring-loaded groovy scripts, marked with refresh-check-delay, which loads them as CGLIB proxies. Invoking a method on the proxy instance, I get

Exception in thread "main" java.lang.IllegalArgumentException: object is not an instance of declaring class
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:86)
        at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:226)
        at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:899)
        at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:740)
        at org.codehaus.groovy.runtime.InvokerHelper.invokePogoMethod(InvokerHelper.java:777)
        at org.codehaus.groovy.runtime.InvokerHelper.invokeMethod(InvokerHelper.java:757)
        at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodN(ScriptBytecodeAdapter.java:167)
        at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethod0(ScriptBytecodeAdapter.java:195)
        at X.doIt(script1.groovy]:5)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:301)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:182)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:149)
        at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:131)
        at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:119)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
        at $Proxy1.doIt(Unknown Source)
        at TestMe.main(TestMe.java:20)
script1.groovy
class X implements Doable {
    def prop
    void doIt() {
        println prop.getClass().getName();
        prop.doIt(); 
    }
}
script2.groovy
class Y {
    void doIt() { println "OK"; }
}
Doable.java
public interface Doable {
    void doIt();
}
spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:lang="http://www.springframework.org/schema/lang"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
       http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.5.xsd">
    
    <lang:groovy id="y" script-source="classpath:groovy/script2.groovy" refresh-check-delay="1000"/>
    <lang:groovy id="x" script-source="classpath:groovy/script1.groovy" refresh-check-delay="1000">
        <lang:property name="prop" ref="y"/>
    </lang:groovy>
</beans>
TestMe.java
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestMe {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctxt = new ClassPathXmlApplicationContext("spring.xml");
        Doable o = (Doable)ctxt.getBean("x");
        o.doIt();
    }
}

Activity

Hide
Paul King added a comment -

add code tags

Show
Paul King added a comment - add code tags
Hide
blackdrag blackdrag added a comment -

since GROOVY-2006 is fixed.. could someone please see if this issue is still actual?

Show
blackdrag blackdrag added a comment - since GROOVY-2006 is fixed.. could someone please see if this issue is still actual?
Hide
Martin Mavrov added a comment -

GROOVY-2006 is marked as fixed in 1.1rc2, while this one is present up to 1.5.4

Show
Martin Mavrov added a comment - GROOVY-2006 is marked as fixed in 1.1rc2, while this one is present up to 1.5.4
Hide
René de Bloois added a comment -

Yes, this problem still exists.

It is caused by the following: the script implements the GroovyObject interface. Spring will then create a proxy which will also implement the GroovyObject interface. That way the groovy runtime thinks it is a groovy object. The proxy class will also respond like a groovy object. The groovy runtime will receive methods from the real groovy object through the proxy. But the method does not belong to the proxy, so an exception occurs when groovy tries to call the method as if it belonged to the proxy.

In other words, the problem is caused by the fact that the groovy runtime does the discovery of the method to call, and then tries to call this method on the proxy object. If the groovy runtime would just call invokeMethod() on the proxy object everything should work fine. Why isn't it done like that?

I found 2 workarounds:
1. Let the script implement an interface that contains all the methods you want to call through the proxy. That way the proxy will also contain those methods.
2. Remove the refresh-check-delay and use prototype="true". That way live objects don't need refreshing so no proxy is needed. But every time a new instance is requested, spring will check if the script has been modified.

Show
René de Bloois added a comment - Yes, this problem still exists. It is caused by the following: the script implements the GroovyObject interface. Spring will then create a proxy which will also implement the GroovyObject interface. That way the groovy runtime thinks it is a groovy object. The proxy class will also respond like a groovy object. The groovy runtime will receive methods from the real groovy object through the proxy. But the method does not belong to the proxy, so an exception occurs when groovy tries to call the method as if it belonged to the proxy. In other words, the problem is caused by the fact that the groovy runtime does the discovery of the method to call, and then tries to call this method on the proxy object. If the groovy runtime would just call invokeMethod() on the proxy object everything should work fine. Why isn't it done like that? I found 2 workarounds: 1. Let the script implement an interface that contains all the methods you want to call through the proxy. That way the proxy will also contain those methods. 2. Remove the refresh-check-delay and use prototype="true". That way live objects don't need refreshing so no proxy is needed. But every time a new instance is requested, spring will check if the script has been modified.
Hide
Martin Mavrov added a comment -

On the other hand, if the proxy does not implement GroovyObject interface, there is another error:

Exception in thread "main" groovy.lang.MissingMethodException: No signature of method: $Proxy0.doIt() is applicable for argument types: () values: {}
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:54)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodN(ScriptBytecodeAdapter.java:169)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethod0(ScriptBytecodeAdapter.java:195)
at X.doIt(script1.groovy]:5)

($Proxy0 is for the bean Y that does not implement the Doable interface)

Show
Martin Mavrov added a comment - On the other hand, if the proxy does not implement GroovyObject interface, there is another error: Exception in thread "main" groovy.lang.MissingMethodException: No signature of method: $Proxy0.doIt() is applicable for argument types: () values: {} at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:54) at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethodN(ScriptBytecodeAdapter.java:169) at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.invokeMethod0(ScriptBytecodeAdapter.java:195) at X.doIt(script1.groovy]:5) ($Proxy0 is for the bean Y that does not implement the Doable interface)
Hide
René de Bloois added a comment -

Maybe my comment didn't come out right. I didn't mean to say that the problem is caused by the fact that the script implements the GroovyObject. It was just step 1 in the complete reasoning that the first paragraph represents.

In your case (the script refresh proxy) the problem would be solved if the groovy runtime was designed to call invokeMethod() on the groovy object proxy.

But there may be problems with this solution too. Other kinds of proxies do also exist. Some only add logic to specific method calls. For example, transaction management is added to all find*, get*, add*, update* methods. But if all method calls go through invokeMethod(), the transaction management will be circumvented. So we would lose the ability to select methods.

So I answered my own question from my previous comment

Show
René de Bloois added a comment - Maybe my comment didn't come out right. I didn't mean to say that the problem is caused by the fact that the script implements the GroovyObject. It was just step 1 in the complete reasoning that the first paragraph represents. In your case (the script refresh proxy) the problem would be solved if the groovy runtime was designed to call invokeMethod() on the groovy object proxy. But there may be problems with this solution too. Other kinds of proxies do also exist. Some only add logic to specific method calls. For example, transaction management is added to all find*, get*, add*, update* methods. But if all method calls go through invokeMethod(), the transaction management will be circumvented. So we would lose the ability to select methods. So I answered my own question from my previous comment
Hide
Paul King added a comment -

If you write your own PostProcessor like the one below:

import org.springframework.scripting.support.ScriptFactoryPostProcessor;
import org.springframework.aop.TargetSource;
import groovy.util.ProxyGenerator;

class GroovyScriptFactoryPostProcessor extends ScriptFactoryPostProcessor {
    protected Object createRefreshableProxy(TargetSource ts, Class[] interfaces) {
        final Object refreshableProxy = super.createRefreshableProxy(ts, interfaces);
        return ProxyGenerator.instantiateDelegateWithBaseClass(null, null, refreshableProxy,
                ts.getTargetClass(), ts.getTargetClass().getName() + "_delegateProxy");
    }
}

and then add the following line into your beans xml file:

<bean class="GroovyScriptFactoryPostProcessor"/>

it seems to work with the latest HEAD version of Groovy.

Show
Paul King added a comment - If you write your own PostProcessor like the one below:
import org.springframework.scripting.support.ScriptFactoryPostProcessor;
import org.springframework.aop.TargetSource;
import groovy.util.ProxyGenerator;

class GroovyScriptFactoryPostProcessor extends ScriptFactoryPostProcessor {
    protected Object createRefreshableProxy(TargetSource ts, Class[] interfaces) {
        final Object refreshableProxy = super.createRefreshableProxy(ts, interfaces);
        return ProxyGenerator.instantiateDelegateWithBaseClass(null, null, refreshableProxy,
                ts.getTargetClass(), ts.getTargetClass().getName() + "_delegateProxy");
    }
}
and then add the following line into your beans xml file:
<bean class="GroovyScriptFactoryPostProcessor"/>
it seems to work with the latest HEAD version of Groovy.
Hide
blackdrag blackdrag added a comment -

just to answer the invokeMethod question.... The normal semantics for invokeMethod is to be invoked when no method was found. So a change would mean a semantic change.. but if the proxy would also implement Interceptable, then there would be no problem, since then the runtime will always invoke through invokeMethod.

Show
blackdrag blackdrag added a comment - just to answer the invokeMethod question.... The normal semantics for invokeMethod is to be invoked when no method was found. So a change would mean a semantic change.. but if the proxy would also implement Interceptable, then there would be no problem, since then the runtime will always invoke through invokeMethod.
Hide
Martin Mavrov added a comment -

Forcing the proxy to implement groovy.lang.GroovyInterceptable (via custom Spring bean post-processor) actually works!

Perhaps we should notify the Spring team about this issue and the solution, so that they integrate it into next Spring releases?

Show
Martin Mavrov added a comment - Forcing the proxy to implement groovy.lang.GroovyInterceptable (via custom Spring bean post-processor) actually works! Perhaps we should notify the Spring team about this issue and the solution, so that they integrate it into next Spring releases?
Hide
René de Bloois added a comment -

Why didn't I think of that?

Isn't it much simpler to just add the GroovyInterceptable interface to the Groovy script you made, the proxy will then automatically implement that interface too. Much easier and more selective then using a post-processor. Didn't test this yet however.

I don't think we should let Spring do anything about this.
As I said, transaction management proxies will suffer when the invokeMethod() method is not marked as being transactional.
I think this is a groovy issue, which is easily solved if we add the workarounds we came up with to the groovy wiki.

Show
René de Bloois added a comment - Why didn't I think of that? Isn't it much simpler to just add the GroovyInterceptable interface to the Groovy script you made, the proxy will then automatically implement that interface too. Much easier and more selective then using a post-processor. Didn't test this yet however. I don't think we should let Spring do anything about this. As I said, transaction management proxies will suffer when the invokeMethod() method is not marked as being transactional. I think this is a groovy issue, which is easily solved if we add the workarounds we came up with to the groovy wiki.
Hide
Paul King added a comment -

Removing 1.5.5 as fix version as this isn't critical and is mostly a case of updating our doco.

Show
Paul King added a comment - Removing 1.5.5 as fix version as this isn't critical and is mostly a case of updating our doco.
Hide
Alexander Kleymenov added a comment -

groovy-1.5.6 works fine if Groovy script implements GroovyInterceptable interface.
But groovy-1.6-beta-1 fails after bean refresh with
java.lang.UnsupportedOperationException
Approach with GroovyScriptFactoryPostProcessor also does not work...
Please, use attached test to reproduce the problem: tune JAVA_HOME, GROOVY_HOME, and place spring.jar near bat launchers.

Show
Alexander Kleymenov added a comment - groovy-1.5.6 works fine if Groovy script implements GroovyInterceptable interface. But groovy-1.6-beta-1 fails after bean refresh with java.lang.UnsupportedOperationException Approach with GroovyScriptFactoryPostProcessor also does not work... Please, use attached test to reproduce the problem: tune JAVA_HOME, GROOVY_HOME, and place spring.jar near bat launchers.
Hide
Alexander Kleymenov added a comment -

Not reproducible on groovy-1.6-beta-2.

Show
Alexander Kleymenov added a comment - Not reproducible on groovy-1.6-beta-2.
Hide
Guillaume Laforge added a comment -

Alexander, is it working fine for you too with 1.5.7?

Show
Guillaume Laforge added a comment - Alexander, is it working fine for you too with 1.5.7?
Hide
Alexander Kleymenov added a comment -

Guillaume, yes it works on 1.5.7 too!
This workaround (implementing GroovyInterceptable marker interface to make groovy classes interceptable with Spring AOP) could be really usefull info on the groovy wiki!

Show
Alexander Kleymenov added a comment - Guillaume, yes it works on 1.5.7 too! This workaround (implementing GroovyInterceptable marker interface to make groovy classes interceptable with Spring AOP) could be really usefull info on the groovy wiki!
Hide
Guillaume Laforge added a comment -

Thank you everybody for your feedback and reports saying the problem is solved.

Show
Guillaume Laforge added a comment - Thank you everybody for your feedback and reports saying the problem is solved.

People

Vote (0)
Watch (3)

Dates

  • Created:
    Updated:
    Resolved: