Groovy
  1. Groovy
  2. GROOVY-2583

"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
    • 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

      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();
          }
      }
      
      1. test.zip
        1 kB
        Alexander Kleymenov

        Activity

        Hide
        Paul King added a comment -

        add code tags

        Show
        Paul King added a comment - add code tags
        Hide
        Jochen Theodorou added a comment -

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

        Show
        Jochen Theodorou 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
        Jochen Theodorou 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
        Jochen Theodorou 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 Delcroix added a comment -

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

        Show
        Guillaume Delcroix 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 Delcroix added a comment -

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

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

          People

          • Assignee:
            Unassigned
            Reporter:
            Martin Mavrov
          • Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development