Hibernate proxies Groovy's getMetaClass method breaking proxies when used with Groovy

Description

Say you have a class hierarchy like A->B-C and you obtain a proxied instance of C

When Groovy invokes a method it looks up the objects metaclass by calling the getMetaClass() method on A. The pseudo code for Groovy method dispatch is something like:

The problem is that the getMetaClass() method is being proxied onto the underlying instance of C so when a.getMetaClass() is called you get the MetaClass for the class C, but because of the way Hibernate creates proxies it is not an instance of C at all, but instead an dynamically created subclass of A. The result is you get an IllegalArgumentException that is hard to debug.

In Grails we have worked around this by customizing Hibernate's proxy creation mechanism to not proxy the getMetaClass() method. The problem this mechanism is currently insanely difficult to customize and also usage of Groovy on its own outside of Grails is still broken with Hibernate (such as inside JBoss Seam)

It would be great if one of two things could be done:

a) Hibernate does not proxy the getMetaClass() method at all this could be done by modifying JavassistLazyInitializer and the associated cglib one as follows:

b) The mechanism for creating proxies is made easier to customize so that the getMetaClass() method can be easily excluded via some configuration or something. However, this would still mean that out of the box Hibernate is broken when used with Groovy unless the solution a) is applied too

Environment

None

Activity

Show:
Mark Spritzler
September 21, 2017, 3:19 PM

I would like to thank the Hibernate team on this being resolved. This is awesome.

Vlad Mihalcea
September 21, 2017, 12:30 PM

Applied PR upstream.

Emmanuel Bernard
August 31, 2017, 3:20 PM

Sorry for dropping the subject. will trya nd look at this next week but will need your help to thoroughly test this. Because if we screw up and if getMetaClass needs the state in the end we will break Grails and Groovy apps al over the place.

To summarize, I think my previous self was proposing to filter Groovy's getMetaClass() calls in the proxy and not trigger the initialize method call. Of course we can't depend on Grovvy so we would need to do some String comparison and make sure it's not a non Groovy getter that happens to be called getMetaClass.

Jochen Theodorou
August 19, 2017, 7:51 PM

I must confess I neither fully understand the issue, nor do I know what to do against the problem from the groovy side. I know only that a synthetic/bridge flag is no solution, since that makes problems in the IDE. I also do not know Hibernate enough to say if setting the annotation on the base class that implements GroovyObject is enough, because the methods you override here are form the GroovyObject interface. And then there is the general mechanism. If I invoke a method on a class C and this class implements GroovyObject, then the first thing, the runtime will do is using getMetaClass to get the meta class for C. For a proxy object that instead extends A but proxies an instance of C to work correctly I actually would need a meta class of A or the proxy class extending A. To get this result we would require the proxy not to proxy getMetaClass.

Groovy has made changes to ensure a method call using reflection is always done using the most general base class possible. That means for toString we would always use Object. for a getMetaClass call we would always use GroovyObject. This means that the situation is different from 2009 and if you get the meta class of C, you can still use it form method calls on the proxy of the C instance that is based on A, as long as you stick to methods from A.

I think for Roger the issue is actually different. I thought that adding @Transient would prevent Hibernate from persisting the meta class. Is this not the case?

Roger Diller
August 19, 2017, 4:30 PM
Edited

I added both @Transient flavors onto overridden "getMetaClass" method on my base entity class for good measure:

but I still get:

Fixed

Assignee

Vlad Mihalcea

Reporter

Graeme Rocher

Fix versions

Labels

None

backPortable

None

Suitable for new contributors

None

Requires Release Note

None

Pull Request

None

backportDecision

None

Affects versions

Priority

Critical